Compare commits
37 Commits
v0.104.2
...
102-dns-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9efc39306 | ||
|
|
f924523f6a | ||
|
|
6e6ee9697a | ||
|
|
aff09211b2 | ||
|
|
bad1c6acdc | ||
|
|
fcb582679e | ||
|
|
6b60598025 | ||
|
|
b338bf9b3f | ||
|
|
2313eda123 | ||
|
|
e4383189a5 | ||
|
|
09b6eba7d9 | ||
|
|
9b963fc777 | ||
|
|
7f29d4e546 | ||
|
|
a572876775 | ||
|
|
63e513e33e | ||
|
|
dfd28b45ab | ||
|
|
a86172fda4 | ||
|
|
8501a85292 | ||
|
|
4134220c54 | ||
|
|
ab8defdb08 | ||
|
|
7d1d87d6ec | ||
|
|
641db73a86 | ||
|
|
6e615c6eaa | ||
|
|
60d72fb9c3 | ||
|
|
a6e18c4700 | ||
|
|
f9e4e7b024 | ||
|
|
96e83a133f | ||
|
|
b4a35fa887 | ||
|
|
36c7735b85 | ||
|
|
284da7c91b | ||
|
|
e685d81c92 | ||
|
|
1cf9848044 | ||
|
|
c129361e55 | ||
|
|
046ec13fdc | ||
|
|
3045da1742 | ||
|
|
642dcd647c | ||
|
|
62a8fe0b73 |
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
|
||||
|
||||
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
|
||||
|
||||
305
.github/workflows/build.yml
vendored
305
.github/workflows/build.yml
vendored
@@ -1,170 +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: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Docker Buildx (build)
|
||||
run: |
|
||||
make docker-multi-arch
|
||||
|
||||
notify:
|
||||
needs: [app, docker]
|
||||
# Secrets are not passed to workflows that are triggered by a pull request from a fork
|
||||
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Conclusion
|
||||
uses: technote-space/workflow-conclusion-action@v1
|
||||
-
|
||||
name: Send Slack notif
|
||||
uses: 8398a7/action-slack@v3
|
||||
with:
|
||||
status: ${{ env.WORKFLOW_CONCLUSION }}
|
||||
fields: repo,message,commit,author
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
'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 }}'
|
||||
|
||||
102
.github/workflows/lint.yml
vendored
102
.github/workflows/lint.yml
vendored
@@ -1,47 +1,55 @@
|
||||
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@v2.3.0
|
||||
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.32
|
||||
|
||||
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': '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@v2.3.0'
|
||||
'with':
|
||||
# This field is required. Don't set the patch version to always use
|
||||
# the latest patch version.
|
||||
'version': 'v1.32'
|
||||
'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.
|
||||
#
|
||||
# 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 }}'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@
|
||||
/querylog.json
|
||||
/querylog.json.1
|
||||
coverage.txt
|
||||
leases.db
|
||||
|
||||
# Test output
|
||||
dnsfilter/tests/top-1m.csv
|
||||
|
||||
116
.golangci.yml
116
.golangci.yml
@@ -1,79 +1,77 @@
|
||||
# options for analysis running
|
||||
run:
|
||||
'run':
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
'concurrency': 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 2m
|
||||
'deadline': '2m'
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# 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/.*
|
||||
|
||||
'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:
|
||||
'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
|
||||
'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
|
||||
'linters':
|
||||
'enable':
|
||||
- 'bodyclose'
|
||||
- 'deadcode'
|
||||
- 'depguard'
|
||||
- 'dupl'
|
||||
- 'errcheck'
|
||||
- 'gocyclo'
|
||||
- 'goimports'
|
||||
- 'golint'
|
||||
- 'gosec'
|
||||
- 'govet'
|
||||
- 'ineffassign'
|
||||
- 'misspell'
|
||||
- 'staticcheck'
|
||||
- 'stylecheck'
|
||||
- 'unconvert'
|
||||
- 'unused'
|
||||
- 'varcheck'
|
||||
'disable-all': true
|
||||
'fast': true
|
||||
|
||||
issues:
|
||||
'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
|
||||
'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'
|
||||
|
||||
199
.goreleaser.yml
199
.goreleaser.yml
@@ -1,106 +1,107 @@
|
||||
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:
|
||||
# Add the "netrwork-bind" plug to bind to interfaces.
|
||||
- network-bind
|
||||
# Add the "netrwork-control" 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-control
|
||||
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'
|
||||
|
||||
@@ -1558,6 +1558,7 @@ Strict matching can be enabled by enclosing the value in double quotes: e.g. `"a
|
||||
* blocked_services - blocked services
|
||||
* blocked_safebrowsing - blocked by safebrowsing
|
||||
* blocked_parental - blocked by parental control
|
||||
* blocked_dns_rebinding - blocked by DNS rebinding protection
|
||||
* whitelisted - whitelisted
|
||||
* rewritten - all kinds of rewrites
|
||||
* safe_search - enforced safe search
|
||||
|
||||
89
CHANGELOG.md
89
CHANGELOG.md
@@ -9,24 +9,99 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
<!--
|
||||
## [v0.105.0] - 2020-12-28
|
||||
-->
|
||||
|
||||
### Added
|
||||
|
||||
- Detecting of network interface configurated to have static IP address via
|
||||
`/etc/network/interfaces` ([#2302]).
|
||||
- DNSCrypt protocol support [#1361].
|
||||
- A 5 second wait period until a DHCP server's network interface gets an IP
|
||||
address ([#2304]).
|
||||
- `$dnstype` modifier for filters ([#2337]).
|
||||
- HTTP API request body size limit ([#2305]).
|
||||
|
||||
[#1361]: https://github.com/AdguardTeam/AdGuardHome/issues/1361
|
||||
[#2302]: https://github.com/AdguardTeam/AdGuardHome/issues/2302
|
||||
[#2304]: https://github.com/AdguardTeam/AdGuardHome/issues/2304
|
||||
[#2305]: https://github.com/AdguardTeam/AdGuardHome/issues/2305
|
||||
[#2337]: https://github.com/AdguardTeam/AdGuardHome/issues/2337
|
||||
|
||||
### Changed
|
||||
|
||||
- Post-updating relaunch possibility is now determined OS-dependently ([#2231], [#2391]).
|
||||
- Made the mobileconfig HTTP API more robust and predictable, add parameters and
|
||||
improve error response ([#2358]).
|
||||
- Improved HTTP requests handling and timeouts ([#2343]).
|
||||
- Our snap package now uses the `core20` image as its base ([#2306]).
|
||||
- Various internal improvements ([#2271], [#2297]).
|
||||
|
||||
[#2231]: https://github.com/AdguardTeam/AdGuardHome/issues/2231
|
||||
[#2271]: https://github.com/AdguardTeam/AdGuardHome/issues/2271
|
||||
[#2297]: https://github.com/AdguardTeam/AdGuardHome/issues/2297
|
||||
[#2306]: https://github.com/AdguardTeam/AdGuardHome/issues/2306
|
||||
[#2343]: https://github.com/AdguardTeam/AdGuardHome/issues/2343
|
||||
[#2358]: https://github.com/AdguardTeam/AdGuardHome/issues/2358
|
||||
[#2391]: https://github.com/AdguardTeam/AdGuardHome/issues/2391
|
||||
|
||||
### Fixed
|
||||
|
||||
- A mitigation against records being shown in the wrong order on the query log
|
||||
page ([#2293]).
|
||||
- A JSON parsing error in query log ([#2345]).
|
||||
- Incorrect detection of the IPv6 address of an interface as well as another
|
||||
infinite loop in the `/dhcp/find_active_dhcp` HTTP API ([#2355]).
|
||||
|
||||
[#2293]: https://github.com/AdguardTeam/AdGuardHome/issues/2293
|
||||
[#2345]: https://github.com/AdguardTeam/AdGuardHome/issues/2345
|
||||
[#2355]: https://github.com/AdguardTeam/AdGuardHome/issues/2355
|
||||
|
||||
|
||||
|
||||
## [v0.104.3] - 2020-11-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- The accidentally exposed profiler HTTP API ([#2336]).
|
||||
|
||||
[#2336]: https://github.com/AdguardTeam/AdGuardHome/issues/2336
|
||||
|
||||
|
||||
|
||||
## [v0.104.2] - 2020-11-19
|
||||
|
||||
### Added
|
||||
|
||||
- This changelog :-) (#2294).
|
||||
- This changelog :-) ([#2294]).
|
||||
- `HACKING.md`, a guide for developers.
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved tests output (#2273).
|
||||
- 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).
|
||||
- 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).
|
||||
correctly shows that DHCP is not currently available on that OS ([#2295]).
|
||||
- Infinite loop in `/dhcp/find_active_dhcp` ([#2301]).
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.104.2...HEAD
|
||||
[#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
|
||||
|
||||
132
HACKING.md
132
HACKING.md
@@ -1,10 +1,17 @@
|
||||
# AdGuardHome Developer Guidelines
|
||||
# *AdGuardHome* Developer Guidelines
|
||||
|
||||
As of **2020-11-12**, this document is still a work-in-progress. Some of the
|
||||
rules aren't enforced, and others might change. Still, this is a good place to
|
||||
find out about how we **want** our code to look like.
|
||||
As of **2020-11-27**, this document is a work-in-progress, but should still be
|
||||
followed. Some of the rules aren't enforced as thoroughly or remain broken in
|
||||
old code, but this is still the place to find out about what we **want** our
|
||||
code to look like.
|
||||
|
||||
## Git
|
||||
The rules are mostly sorted in the alphabetical order.
|
||||
|
||||
## *Git*
|
||||
|
||||
* Call your branches either `NNNN-fix-foo` (where `NNNN` is the ID of the
|
||||
*GitHub* issue you worked on in this branch) or just `fix-foo` if there was
|
||||
no *GitHub* issue.
|
||||
|
||||
* Follow the commit message header format:
|
||||
|
||||
@@ -13,28 +20,28 @@ find out about how we **want** our code to look like.
|
||||
```
|
||||
|
||||
Where `pkg` is the package where most changes took place. If there are
|
||||
several such packages, just write `all`.
|
||||
several such packages, or the change is top-level only, write `all`.
|
||||
|
||||
* Keep your commit messages to be no wider than eighty (**80**) columns.
|
||||
* Keep your commit messages, including headers, to eighty (**80**) columns.
|
||||
|
||||
* Only use lowercase letters in your commit message headers.
|
||||
* Only use lowercase letters in your commit message headers. The rest of the
|
||||
message should follow the plain text conventions below.
|
||||
|
||||
## Go
|
||||
The only exceptions are direct mentions of identifiers from the source code
|
||||
and filenames like `HACKING.md`.
|
||||
|
||||
* <https://github.com/golang/go/wiki/CodeReviewComments>.
|
||||
## *Go*
|
||||
|
||||
* <https://github.com/golang/go/wiki/TestComments>.
|
||||
### Code And Naming
|
||||
|
||||
* <https://go-proverbs.github.io/>
|
||||
* Avoid `goto`.
|
||||
|
||||
* Avoid `init` and use explicit initialization functions instead.
|
||||
|
||||
* Avoid `new`, especially with structs.
|
||||
|
||||
* Document everything, including unexported top-level identifiers, to build
|
||||
a habit of writing documentation.
|
||||
|
||||
* Don't put variable names into any kind of quotes.
|
||||
* Constructors should validate their arguments and return meaningful errors.
|
||||
As a corollary, avoid lazy initialization.
|
||||
|
||||
* Don't use naked `return`s.
|
||||
|
||||
@@ -48,7 +55,17 @@ find out about how we **want** our code to look like.
|
||||
* Eschew external dependencies, including transitive, unless
|
||||
absolutely necessary.
|
||||
|
||||
* No `goto`.
|
||||
* Name benchmarks and tests using the same convention as examples. For
|
||||
example:
|
||||
|
||||
```go
|
||||
func TestFunction(t *testing.T) { /* … */ }
|
||||
func TestFunction_suffix(t *testing.T) { /* … */ }
|
||||
func TestType_Method(t *testing.T) { /* … */ }
|
||||
func TestType_Method_suffix(t *testing.T) { /* … */ }
|
||||
```
|
||||
|
||||
* Name the deferred errors (e.g. when closing something) `cerr`.
|
||||
|
||||
* No shadowing, since it can often lead to subtle bugs, especially with
|
||||
errors.
|
||||
@@ -56,17 +73,28 @@ find out about how we **want** our code to look like.
|
||||
* Prefer constants to variables where possible. Reduce global variables. Use
|
||||
[constant errors] instead of `errors.New`.
|
||||
|
||||
* Put comments above the documented entity, **not** to the side, to improve
|
||||
readability.
|
||||
|
||||
* Use `gofumpt --extra -s`.
|
||||
|
||||
**TODO(a.garipov):** Add to the linters.
|
||||
|
||||
* 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:
|
||||
|
||||
@@ -77,8 +105,14 @@ find out about how we **want** our code to look like.
|
||||
}
|
||||
```
|
||||
|
||||
* Write logs and error messages in lowercase only to make it easier to `grep`
|
||||
logs and error messages without using the `-i` flag.
|
||||
### Formatting
|
||||
|
||||
* Add an empty line before `break`, `continue`, `fallthrough`, and `return`,
|
||||
unless it's the only statement in that block.
|
||||
|
||||
* Use `gofumpt --extra -s`.
|
||||
|
||||
**TODO(a.garipov):** Add to the linters.
|
||||
|
||||
* Write slices of struct like this:
|
||||
|
||||
@@ -95,11 +129,28 @@ find out about how we **want** our code to look like.
|
||||
}}
|
||||
```
|
||||
|
||||
[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
|
||||
### Recommended Reading
|
||||
|
||||
* <https://github.com/golang/go/wiki/CodeReviewComments>.
|
||||
|
||||
* <https://github.com/golang/go/wiki/TestComments>.
|
||||
|
||||
* <https://go-proverbs.github.io/>
|
||||
|
||||
## *Markdown*
|
||||
|
||||
* **TODO(a.garipov):** Define our *Markdown* conventions.
|
||||
|
||||
## Text, Including Comments
|
||||
|
||||
* End sentences with appropriate punctuation.
|
||||
|
||||
* Headers should be written with all initial letters capitalized, except for
|
||||
references to variable names that start with a lowercase letter.
|
||||
|
||||
* Start sentences with a capital letter, unless the first word is a reference
|
||||
to a variable name that starts with a lowercase letter.
|
||||
|
||||
* Text should wrap at eighty (**80**) columns to be more readable, to use
|
||||
a common standard, and to allow editing or diffing side-by-side without
|
||||
wrapping.
|
||||
@@ -111,7 +162,7 @@ find out about how we **want** our code to look like.
|
||||
|
||||
* Use double spacing between sentences to make sentence borders more clear.
|
||||
|
||||
* Use the serial comma (a.k.a. Oxford comma) to improve comprehension,
|
||||
* Use the serial comma (a.k.a. *Oxford* comma) to improve comprehension,
|
||||
decrease ambiguity, and use a common standard.
|
||||
|
||||
* Write todos like this:
|
||||
@@ -126,17 +177,17 @@ find out about how we **want** our code to look like.
|
||||
// TODO(usr1, usr2): Fix the frobulation issue.
|
||||
```
|
||||
|
||||
## Markdown
|
||||
## *YAML*
|
||||
|
||||
* **TODO(a.garipov):** Define our Markdown conventions.
|
||||
* **TODO(a.garipov):** Define naming conventions for schema names in our
|
||||
*OpenAPI* *YAML* file. And just generally OpenAPI conventions.
|
||||
|
||||
## YAML
|
||||
* **TODO(a.garipov):** Find a *YAML* formatter or write our own.
|
||||
|
||||
* **TODO(a.garipov):** Find a YAML formatter or write our own.
|
||||
* All strings, including keys, must be quoted. Reason: the [*NO-rway Law*].
|
||||
|
||||
* All strings, including keys, must be quoted. Reason: the [NO-rway Law].
|
||||
|
||||
* Indent with two (**2**) spaces.
|
||||
* Indent with two (**2**) spaces. *YAML* documents can get pretty
|
||||
deeply-nested.
|
||||
|
||||
* No extra indentation in multiline arrays:
|
||||
|
||||
@@ -147,7 +198,10 @@ find out about how we **want** our code to look like.
|
||||
- 'value-3'
|
||||
```
|
||||
|
||||
* Prefer single quotes for string to prevent accidental escaping, unless
|
||||
escaping is required.
|
||||
* 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*).
|
||||
|
||||
[NO-rway Law]: https://news.ycombinator.com/item?id=17359376
|
||||
* Use `>` for multiline strings, unless you need to keep the line breaks.
|
||||
|
||||
[*NO-rway Law*]: https://news.ycombinator.com/item?id=17359376
|
||||
|
||||
22
Makefile
22
Makefile
@@ -33,6 +33,7 @@ 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
|
||||
@@ -109,7 +110,7 @@ $(error DOCKER_IMAGE_NAME value is not set)
|
||||
endif
|
||||
|
||||
# OS-specific flags
|
||||
TEST_FLAGS := --race -v
|
||||
TEST_FLAGS := --race $(VERBOSE)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
TEST_FLAGS :=
|
||||
endif
|
||||
@@ -177,20 +178,11 @@ dependencies:
|
||||
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
|
||||
|
||||
docker-multi-arch:
|
||||
DOCKER_CLI_EXPERIMENTAL=enabled \
|
||||
|
||||
@@ -587,5 +587,12 @@
|
||||
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this.",
|
||||
"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"
|
||||
"experimental": "Experimental",
|
||||
"rebinding_title": "DNS Rebinding Protection",
|
||||
"rebinding_desc": "Here you can configure protection against DNS rebinding attacks",
|
||||
"rebinding_protection_enabled": "Enable protection from DNS rebinding attacks",
|
||||
"rebinding_protection_enabled_desc": "If enabled, AdGuard Home will block responses containing host on the local network.",
|
||||
"rebinding_allowed_hosts_title": "Allowed domains",
|
||||
"rebinding_allowed_hosts_desc": "A list of domains. If configured, AdGuard Home will allow responses containing host on the local network from these domains. Here you can specify the exact domain names, wildcards and urlfilter-rules, e.g. 'example.org', '*.example.org' or '||example.org^'.",
|
||||
"blocked_dns_rebinding": "Blocked DNS rebinding"
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ export const setDnsConfig = (config) => async (dispatch) => {
|
||||
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
||||
hasDnsSettings = true;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(data, 'rebinding_allowed_hosts')) {
|
||||
data.rebinding_allowed_hosts = splitByNewLine(config.rebinding_allowed_hosts);
|
||||
hasDnsSettings = true;
|
||||
}
|
||||
|
||||
await apiClient.setDnsConfig(data);
|
||||
|
||||
|
||||
91
client/src/components/Settings/Dns/Rebinding/Form.js
Normal file
91
client/src/components/Settings/Dns/Rebinding/Form.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { renderTextareaField, CheckboxField } from '../../../../helpers/form';
|
||||
import { removeEmptyLines } from '../../../../helpers/helpers';
|
||||
import { FORM_NAME } from '../../../../helpers/constants';
|
||||
|
||||
const fields = [
|
||||
{
|
||||
id: 'rebinding_allowed_hosts',
|
||||
title: 'rebinding_allowed_hosts_title',
|
||||
subtitle: 'rebinding_allowed_hosts_desc',
|
||||
normalizeOnBlur: removeEmptyLines,
|
||||
},
|
||||
];
|
||||
|
||||
const Form = ({
|
||||
handleSubmit, submitting, invalid,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const processingSetConfig = useSelector((state) => state.dnsConfig.processingSetConfig);
|
||||
|
||||
const renderField = ({
|
||||
id, title, subtitle, disabled = processingSetConfig, normalizeOnBlur,
|
||||
}) => <div key={id} className="form__group mb-5">
|
||||
<label className="form__label form__label--with-desc" htmlFor={id}>
|
||||
<Trans>{title}</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>{subtitle}</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id={id}
|
||||
name={id}
|
||||
component={renderTextareaField}
|
||||
type="text"
|
||||
className="form-control form-control--textarea font-monospace"
|
||||
disabled={disabled}
|
||||
normalizeOnBlur={normalizeOnBlur}
|
||||
/>
|
||||
</div>;
|
||||
|
||||
renderField.propTypes = {
|
||||
id: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
normalizeOnBlur: PropTypes.func,
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name={'rebinding_protection_enabled'}
|
||||
type="checkbox"
|
||||
component={CheckboxField}
|
||||
placeholder={t('rebinding_protection_enabled')}
|
||||
subtitle={t('rebinding_protection_enabled_desc')}
|
||||
disabled={processingSetConfig}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{fields.map(renderField)}
|
||||
|
||||
<div className="card-actions">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid || processingSetConfig}
|
||||
>
|
||||
<Trans>save_config</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default reduxForm({ form: FORM_NAME.REBINDING })(Form);
|
||||
36
client/src/components/Settings/Dns/Rebinding/index.js
Normal file
36
client/src/components/Settings/Dns/Rebinding/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import Card from '../../../ui/Card';
|
||||
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||
|
||||
const RebindingConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
rebinding_protection_enabled, rebinding_allowed_hosts,
|
||||
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||
|
||||
const handleFormSubmit = (values) => {
|
||||
dispatch(setDnsConfig(values));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('rebinding_title')}
|
||||
subtitle={t('rebinding_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={{
|
||||
rebinding_protection_enabled,
|
||||
rebinding_allowed_hosts,
|
||||
}}
|
||||
onSubmit={handleFormSubmit}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default RebindingConfig;
|
||||
@@ -8,6 +8,7 @@ import Config from './Config';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
import CacheConfig from './Cache';
|
||||
import RebindingConfig from './Rebinding';
|
||||
import { getDnsConfig } from '../../../actions/dnsConfig';
|
||||
import { getAccessList } from '../../../actions/access';
|
||||
|
||||
@@ -33,6 +34,7 @@ const Dns = () => {
|
||||
<Config />
|
||||
<CacheConfig />
|
||||
<Access />
|
||||
<RebindingConfig />
|
||||
</>}
|
||||
</>;
|
||||
};
|
||||
|
||||
@@ -2,22 +2,25 @@ 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}>
|
||||
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 })}
|
||||
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 })}
|
||||
download>{i18next.t('download_mobileconfig_doh')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>;
|
||||
@@ -38,37 +41,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 +53,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 +60,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 +185,7 @@ const getTabs = ({
|
||||
tlsAddress,
|
||||
httpsAddress,
|
||||
showDnsPrivacyNotice,
|
||||
server_name,
|
||||
t,
|
||||
}) => ({
|
||||
Router: {
|
||||
@@ -277,7 +291,7 @@ const getTabs = ({
|
||||
setup_dns_privacy_3
|
||||
</Trans>
|
||||
</div>
|
||||
{dnsPrivacyList.map(renderDnsPrivacyList)}
|
||||
{getDnsPrivacyList(server_name).map(renderDnsPrivacyList)}
|
||||
</>}
|
||||
</div>
|
||||
</div>;
|
||||
@@ -299,6 +313,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 +324,7 @@ const Guide = ({ dnsAddresses }) => {
|
||||
tlsAddress,
|
||||
httpsAddress,
|
||||
showDnsPrivacyNotice,
|
||||
server_name,
|
||||
t,
|
||||
});
|
||||
|
||||
|
||||
@@ -341,6 +341,7 @@ export const FILTERED_STATUS = {
|
||||
REWRITE_HOSTS: 'RewriteEtcHosts',
|
||||
FILTERED_SAFE_SEARCH: 'FilteredSafeSearch',
|
||||
FILTERED_SAFE_BROWSING: 'FilteredSafeBrowsing',
|
||||
FILTERED_REBIND: 'FilteredRebind',
|
||||
FILTERED_PARENTAL: 'FilteredParental',
|
||||
};
|
||||
|
||||
@@ -373,6 +374,10 @@ export const RESPONSE_FILTER = {
|
||||
QUERY: 'blocked_parental',
|
||||
LABEL: 'blocked_adult_websites',
|
||||
},
|
||||
BLOCKED_DNS_REBINDING: {
|
||||
QUERY: 'blocked_dns_rebinding',
|
||||
LABEL: 'blocked_dns_rebinding',
|
||||
},
|
||||
ALLOWED: {
|
||||
QUERY: 'whitelisted',
|
||||
LABEL: 'allowed',
|
||||
@@ -414,6 +419,10 @@ export const FILTERED_STATUS_TO_META_MAP = {
|
||||
LABEL: 'blocked_service',
|
||||
COLOR: QUERY_STATUS_COLORS.RED,
|
||||
},
|
||||
[FILTERED_STATUS.FILTERED_REBIND]: {
|
||||
LABEL: RESPONSE_FILTER.BLOCKED_DNS_REBINDING.LABEL,
|
||||
COLOR: QUERY_STATUS_COLORS.RED,
|
||||
},
|
||||
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
||||
LABEL: RESPONSE_FILTER.SAFE_SEARCH.LABEL,
|
||||
COLOR: QUERY_STATUS_COLORS.YELLOW,
|
||||
@@ -509,6 +518,7 @@ export const FORM_NAME = {
|
||||
INSTALL: 'install',
|
||||
LOGIN: 'login',
|
||||
CACHE: 'cache',
|
||||
REBINDING: 'rebinding',
|
||||
...DHCP_FORM_NAMES,
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const dnsConfig = handleActions(
|
||||
blocking_ipv6,
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
rebinding_allowed_hosts,
|
||||
...values
|
||||
} = payload;
|
||||
|
||||
@@ -24,8 +25,9 @@ const dnsConfig = handleActions(
|
||||
...values,
|
||||
blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4,
|
||||
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
||||
upstream_dns: (upstream_dns && upstream_dns.join('\n')) || '',
|
||||
bootstrap_dns: (bootstrap_dns && bootstrap_dns.join('\n')) || '',
|
||||
upstream_dns: upstream_dns?.join('\n') || '',
|
||||
bootstrap_dns: bootstrap_dns?.join('\n') || '',
|
||||
rebinding_allowed_hosts: rebinding_allowed_hosts?.join('\n') || '',
|
||||
processingGetConfig: false,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
30
go.mod
30
go.mod
@@ -4,34 +4,40 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.33.2
|
||||
github.com/AdguardTeam/golibs v0.4.3
|
||||
github.com/AdguardTeam/urlfilter v0.12.3
|
||||
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/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8
|
||||
github.com/joomcode/errorx v1.0.3 // indirect
|
||||
github.com/kardianos/service v1.1.0
|
||||
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.35
|
||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sirupsen/logrus v1.6.0 // indirect
|
||||
github.com/sparrc/go-ping v0.0.0-20190613174326-4e5b6552494c
|
||||
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 v6.0.0+incompatible
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
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-20201109165425-215b40eba54c
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5
|
||||
howett.net/plist v0.0.0-20201026045517-117a925f2150
|
||||
)
|
||||
|
||||
292
go.sum
292
go.sum
@@ -2,7 +2,18 @@ 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=
|
||||
@@ -14,19 +25,25 @@ github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjM
|
||||
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/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=
|
||||
@@ -34,30 +51,47 @@ github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaE
|
||||
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=
|
||||
@@ -67,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=
|
||||
@@ -105,6 +157,7 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/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=
|
||||
@@ -113,45 +166,85 @@ 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=
|
||||
@@ -160,38 +253,63 @@ 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.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.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=
|
||||
@@ -200,27 +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=
|
||||
@@ -244,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=
|
||||
@@ -272,58 +419,103 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.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-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/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=
|
||||
@@ -331,6 +523,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
|
||||
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=
|
||||
@@ -342,22 +535,34 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-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=
|
||||
@@ -367,9 +572,16 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/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-20201109165425-215b40eba54c h1:+B+zPA6081G5cEb2triOIJpcvSW4AYzmIyWAqMn2JAc=
|
||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/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=
|
||||
@@ -381,17 +593,33 @@ golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/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=
|
||||
@@ -400,23 +628,38 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-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=
|
||||
@@ -430,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=
|
||||
@@ -437,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=
|
||||
@@ -457,9 +704,12 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
|
||||
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=
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
|
||||
return false, fmt.Errorf("couldn't find interface by name %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
ifaceIPNet, err := ifaceIPv4Addrs(iface)
|
||||
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion4)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting ipv4 addrs for iface %s: %w", ifaceName, err)
|
||||
}
|
||||
@@ -94,12 +94,11 @@ func CheckIfOtherDHCPServersPresentV4(ifaceName string) (bool, error) {
|
||||
|
||||
continue
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +161,7 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
|
||||
return false, fmt.Errorf("dhcpv6: net.InterfaceByName: %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
ifaceIPNet, err := ifaceIPv6Addrs(iface)
|
||||
ifaceIPNet, err := ifaceIPAddrs(iface, ipVersion6)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("getting ipv6 addrs for iface %s: %w", ifaceName, err)
|
||||
}
|
||||
@@ -216,12 +215,11 @@ func CheckIfOtherDHCPServersPresentV6(ifaceName string) (bool, error) {
|
||||
|
||||
continue
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ func (s *Server) dbLoad() {
|
||||
} else {
|
||||
v6DynLeases = append(v6DynLeases, &lease)
|
||||
}
|
||||
|
||||
} else {
|
||||
if obj[i].Expiry == leaseExpireStatic {
|
||||
staticLeases = append(staticLeases, &lease)
|
||||
|
||||
@@ -52,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()
|
||||
@@ -74,16 +75,12 @@ 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{}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/jsonutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -205,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
|
||||
@@ -282,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
|
||||
}
|
||||
}
|
||||
@@ -299,6 +300,7 @@ func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
// . Check if a static IP is configured for the network interface
|
||||
// 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 {
|
||||
msg := fmt.Sprintf("failed to read request body: %s", err)
|
||||
@@ -318,7 +320,7 @@ func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
|
||||
found4, err4 := CheckIfOtherDHCPServersPresentV4(interfaceName)
|
||||
|
||||
staticIP := map[string]interface{}{}
|
||||
isStaticIP, err := HasStaticIP(interfaceName)
|
||||
isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName)
|
||||
staticIPStatus := "yes"
|
||||
if err != nil {
|
||||
staticIPStatus = "error"
|
||||
@@ -508,6 +510,9 @@ func (s *Server) registerHandlers() {
|
||||
}
|
||||
|
||||
// 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"`
|
||||
|
||||
@@ -19,9 +19,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -317,26 +315,6 @@ func WithTimeout(d time.Duration) ClientOpt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithSummaryLogger logs one-line DHCPv4 message summaries when sent & received.
|
||||
func WithSummaryLogger() ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.logger = ShortSummaryLogger{
|
||||
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithDebugLogger logs multi-line full DHCPv4 messages when sent & received.
|
||||
func WithDebugLogger() ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
c.logger = DebugLogger{
|
||||
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// WithLogger set the logger (see interface Logger).
|
||||
func WithLogger(newLogger Logger) ClientOpt {
|
||||
return func(c *Client) (err error) {
|
||||
|
||||
@@ -79,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
|
||||
}
|
||||
@@ -92,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))
|
||||
}
|
||||
@@ -309,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
|
||||
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
|
||||
"github.com/AdguardTeam/golibs/file"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// HasStaticIP check if the network interface has a static IP configured
|
||||
//
|
||||
// Supports: Raspbian.
|
||||
func HasStaticIP(ifaceName string) (bool, error) {
|
||||
if runtime.GOOS == "linux" {
|
||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return hasStaticIPDhcpcdConf(string(body), ifaceName), nil
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
return hasStaticIPDarwin(ifaceName)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("cannot check if IP is static: not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// SetStaticIP sets a static IP for the network interface.
|
||||
func SetStaticIP(ifaceName string) error {
|
||||
if runtime.GOOS == "linux" {
|
||||
return setStaticIPDhcpdConf(ifaceName)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
return setStaticIPDarwin(ifaceName)
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot set static IP on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
// for dhcpcd.conf
|
||||
func hasStaticIPDhcpcdConf(dhcpConf, ifaceName string) bool {
|
||||
lines := strings.Split(dhcpConf, "\n")
|
||||
nameLine := fmt.Sprintf("interface %s", ifaceName)
|
||||
withinInterfaceCtx := false
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if withinInterfaceCtx && len(line) == 0 {
|
||||
// an empty line resets our state
|
||||
withinInterfaceCtx = false
|
||||
}
|
||||
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if !withinInterfaceCtx {
|
||||
if line == nameLine {
|
||||
// we found our interface
|
||||
withinInterfaceCtx = true
|
||||
}
|
||||
} else {
|
||||
if strings.HasPrefix(line, "interface ") {
|
||||
// we found another interface - reset our state
|
||||
withinInterfaceCtx = false
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "static ip_address=") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get gateway IP address
|
||||
func getGatewayIP(ifaceName string) string {
|
||||
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
|
||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||
d, err := cmd.Output()
|
||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
fields := strings.Fields(string(d))
|
||||
if len(fields) < 3 || fields[0] != "default" {
|
||||
return ""
|
||||
}
|
||||
|
||||
ip := net.ParseIP(fields[2])
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fields[2]
|
||||
}
|
||||
|
||||
// setStaticIPDhcpdConf - updates /etc/dhcpd.conf and sets the current IP address to be static
|
||||
func setStaticIPDhcpdConf(ifaceName string) error {
|
||||
ip := util.GetSubnet(ifaceName)
|
||||
if len(ip) == 0 {
|
||||
return errors.New("can't get IP address")
|
||||
}
|
||||
|
||||
ip4, _, err := net.ParseCIDR(ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gatewayIP := getGatewayIP(ifaceName)
|
||||
add := updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, ip4.String())
|
||||
|
||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body = append(body, []byte(add)...)
|
||||
err = file.SafeWrite("/etc/dhcpcd.conf", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updates dhcpd.conf content -- sets static IP address there
|
||||
// for dhcpcd.conf
|
||||
func updateStaticIPDhcpcdConf(ifaceName, ip, gatewayIP, dnsIP string) string {
|
||||
var body []byte
|
||||
|
||||
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
|
||||
ifaceName, ip)
|
||||
body = append(body, []byte(add)...)
|
||||
|
||||
if len(gatewayIP) != 0 {
|
||||
add = fmt.Sprintf("static routers=%s\n",
|
||||
gatewayIP)
|
||||
body = append(body, []byte(add)...)
|
||||
}
|
||||
|
||||
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
|
||||
dnsIP)
|
||||
body = append(body, []byte(add)...)
|
||||
|
||||
return string(body)
|
||||
}
|
||||
|
||||
// Check if network interface has a static IP configured
|
||||
// Supports: MacOS.
|
||||
func hasStaticIPDarwin(ifaceName string) (bool, error) {
|
||||
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return portInfo.static, nil
|
||||
}
|
||||
|
||||
// setStaticIPDarwin - uses networksetup util to set the current IP address to be static
|
||||
// Additionally it configures the current DNS servers as well
|
||||
func setStaticIPDarwin(ifaceName string) error {
|
||||
portInfo, err := getCurrentHardwarePortInfo(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if portInfo.static {
|
||||
return errors.New("IP address is already static")
|
||||
}
|
||||
|
||||
dnsAddrs, err := getEtcResolvConfServers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := make([]string, 0)
|
||||
args = append(args, "-setdnsservers", portInfo.name)
|
||||
args = append(args, dnsAddrs...)
|
||||
|
||||
// Setting DNS servers is necessary when configuring a static IP
|
||||
code, _, err := util.RunCommand("networksetup", args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return fmt.Errorf("failed to set DNS servers, code=%d", code)
|
||||
}
|
||||
|
||||
// Actually configures hardware port to have static IP
|
||||
code, _, err = util.RunCommand("networksetup", "-setmanual",
|
||||
portInfo.name, portInfo.ip, portInfo.subnet, portInfo.gatewayIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if code != 0 {
|
||||
return fmt.Errorf("failed to set DNS servers, code=%d", code)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCurrentHardwarePortInfo gets information the specified network interface
|
||||
func getCurrentHardwarePortInfo(ifaceName string) (hardwarePortInfo, error) {
|
||||
// First of all we should find hardware port name
|
||||
m := getNetworkSetupHardwareReports()
|
||||
hardwarePort, ok := m[ifaceName]
|
||||
if !ok {
|
||||
return hardwarePortInfo{}, fmt.Errorf("could not find hardware port for %s", ifaceName)
|
||||
}
|
||||
|
||||
return getHardwarePortInfo(hardwarePort)
|
||||
}
|
||||
|
||||
// getNetworkSetupHardwareReports parses the output of the `networksetup -listallhardwareports` command
|
||||
// it returns a map where the key is the interface name, and the value is the "hardware port"
|
||||
// returns nil if it fails to parse the output
|
||||
func getNetworkSetupHardwareReports() map[string]string {
|
||||
_, out, err := util.RunCommand("networksetup", "-listallhardwareports")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
re, err := regexp.Compile("Hardware Port: (.*?)\nDevice: (.*?)\n")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := make(map[string]string)
|
||||
|
||||
matches := re.FindAllStringSubmatch(out, -1)
|
||||
for i := range matches {
|
||||
port := matches[i][1]
|
||||
device := matches[i][2]
|
||||
m[device] = port
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// hardwarePortInfo - information obtained using MacOS networksetup
|
||||
// about the current state of the internet connection
|
||||
type hardwarePortInfo struct {
|
||||
name string
|
||||
ip string
|
||||
subnet string
|
||||
gatewayIP string
|
||||
static bool
|
||||
}
|
||||
|
||||
func getHardwarePortInfo(hardwarePort string) (hardwarePortInfo, error) {
|
||||
h := hardwarePortInfo{}
|
||||
|
||||
_, out, err := util.RunCommand("networksetup", "-getinfo", hardwarePort)
|
||||
if err != nil {
|
||||
return h, err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("IP address: (.*?)\nSubnet mask: (.*?)\nRouter: (.*?)\n")
|
||||
|
||||
match := re.FindStringSubmatch(out)
|
||||
if len(match) == 0 {
|
||||
return h, errors.New("could not find hardware port info")
|
||||
}
|
||||
|
||||
h.name = hardwarePort
|
||||
h.ip = match[1]
|
||||
h.subnet = match[2]
|
||||
h.gatewayIP = match[3]
|
||||
|
||||
if strings.Index(out, "Manual Configuration") == 0 {
|
||||
h.static = true
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Gets a list of nameservers currently configured in the /etc/resolv.conf
|
||||
func getEtcResolvConfServers() ([]string, error) {
|
||||
body, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("nameserver ([a-zA-Z0-9.:]+)")
|
||||
|
||||
matches := re.FindAllStringSubmatch(string(body), -1)
|
||||
if len(matches) == 0 {
|
||||
return nil, errors.New("found no DNS servers in /etc/resolv.conf")
|
||||
}
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for i := range matches {
|
||||
addrs = append(addrs, matches[i][1])
|
||||
}
|
||||
|
||||
return addrs, nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHasStaticIPDhcpcdConf(t *testing.T) {
|
||||
dhcpdConf := `#comment
|
||||
# comment
|
||||
|
||||
interface eth0
|
||||
static ip_address=192.168.0.1/24
|
||||
|
||||
# interface wlan0
|
||||
static ip_address=192.168.1.1/24
|
||||
|
||||
# comment
|
||||
`
|
||||
assert.True(t, !hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
|
||||
|
||||
dhcpdConf = `#comment
|
||||
# comment
|
||||
|
||||
interface eth0
|
||||
static ip_address=192.168.0.1/24
|
||||
|
||||
# interface wlan0
|
||||
static ip_address=192.168.1.1/24
|
||||
|
||||
# comment
|
||||
|
||||
interface wlan0
|
||||
# comment
|
||||
static ip_address=192.168.2.1/24
|
||||
`
|
||||
assert.True(t, hasStaticIPDhcpcdConf(dhcpdConf, "wlan0"))
|
||||
}
|
||||
|
||||
func TestSetStaticIPDhcpcdConf(t *testing.T) {
|
||||
dhcpcdConf := `
|
||||
interface wlan0
|
||||
static ip_address=192.168.0.2/24
|
||||
static routers=192.168.0.1
|
||||
static domain_name_servers=192.168.0.2
|
||||
|
||||
`
|
||||
s := updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "192.168.0.1", "192.168.0.2")
|
||||
assert.Equal(t, dhcpcdConf, s)
|
||||
|
||||
// without gateway
|
||||
dhcpcdConf = `
|
||||
interface wlan0
|
||||
static ip_address=192.168.0.2/24
|
||||
static domain_name_servers=192.168.0.2
|
||||
|
||||
`
|
||||
s = updateStaticIPDhcpcdConf("wlan0", "192.168.0.2/24", "", "192.168.0.2")
|
||||
assert.Equal(t, dhcpcdConf, s)
|
||||
}
|
||||
@@ -180,15 +180,18 @@ func (ra *raCtx) Init() error {
|
||||
data := createICMPv6RAPacket(params)
|
||||
|
||||
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: %w", err)
|
||||
}
|
||||
success := false
|
||||
defer func() {
|
||||
if !success {
|
||||
ra.Close()
|
||||
cerr := ra.Close()
|
||||
if cerr != nil {
|
||||
log.Error("closing context: %s", cerr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -227,13 +230,15 @@ func (ra *raCtx) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close - close module
|
||||
func (ra *raCtx) Close() {
|
||||
// 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
|
||||
@@ -244,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
|
||||
}
|
||||
|
||||
@@ -255,7 +258,12 @@ func (s *v4Server) addrAvailable(target net.IP) bool {
|
||||
reply = true
|
||||
}
|
||||
log.Debug("dhcpv4: Sending ICMP Echo to %v", target)
|
||||
pinger.Run()
|
||||
|
||||
err = pinger.Run()
|
||||
if err != nil {
|
||||
log.Error("pinger.Run(): %v", err)
|
||||
return true
|
||||
}
|
||||
|
||||
if reply {
|
||||
log.Info("dhcpv4: IP conflict: %v is already used by another device", target)
|
||||
@@ -554,27 +562,6 @@ func (s *v4Server) packetHandler(conn net.PacketConn, peer net.Addr, req *dhcpv4
|
||||
}
|
||||
}
|
||||
|
||||
// ifaceIPv4Addrs returns the interface's IPv4 addresses.
|
||||
func ifaceIPv4Addrs(iface *net.Interface) (ips []net.IP, err error) {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip := ipnet.IP.To4(); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// Start starts the IPv4 DHCP server.
|
||||
func (s *v4Server) Start() error {
|
||||
if !s.conf.Enabled {
|
||||
@@ -589,26 +576,14 @@ func (s *v4Server) Start() error {
|
||||
|
||||
log.Debug("dhcpv4: starting...")
|
||||
|
||||
dnsIPAddrs, err := ifaceIPv4Addrs(iface)
|
||||
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion4, defaultMaxAttempts, defaultBackoff)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv4: getting ipv4 addrs for iface %s: %w", ifaceName, err)
|
||||
return fmt.Errorf("dhcpv4: interface %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
switch len(dnsIPAddrs) {
|
||||
case 0:
|
||||
log.Debug("dhcpv4: no ipv4 address for interface %s", iface.Name)
|
||||
|
||||
if len(dnsIPAddrs) == 0 {
|
||||
// No available IP addresses which may appear later.
|
||||
return nil
|
||||
case 1:
|
||||
// Some Android devices use 8.8.8.8 if there is no secondary DNS
|
||||
// server. Fix that by setting the secondary DNS address to our
|
||||
// address as well.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/AdGuardHome/issues/1708.
|
||||
log.Debug("dhcpv4: setting secondary dns ip to iself for interface %s", iface.Name)
|
||||
dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0])
|
||||
default:
|
||||
// Go on.
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,47 +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 {
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
@@ -537,27 +539,6 @@ func (s *v6Server) packetHandler(conn net.PacketConn, peer net.Addr, req dhcpv6.
|
||||
}
|
||||
}
|
||||
|
||||
// ifaceIPv6Addrs returns the interface's IPv6 addresses.
|
||||
func ifaceIPv6Addrs(iface *net.Interface) (ips []net.IP, err error) {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip := ipnet.IP.To16(); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// initialize RA module
|
||||
func (s *v6Server) initRA(iface *net.Interface) error {
|
||||
// choose the source IP address - should be link-local-unicast
|
||||
@@ -591,24 +572,16 @@ func (s *v6Server) Start() error {
|
||||
return fmt.Errorf("dhcpv6: finding interface %s by name: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
log.Debug("dhcpv4: starting...")
|
||||
log.Debug("dhcpv6: starting...")
|
||||
|
||||
dnsIPAddrs, err := ifaceIPv6Addrs(iface)
|
||||
dnsIPAddrs, err := ifaceDNSIPAddrs(iface, ipVersion6, defaultMaxAttempts, defaultBackoff)
|
||||
if err != nil {
|
||||
return fmt.Errorf("dhcpv6: getting ipv6 addrs for iface %s: %w", ifaceName, err)
|
||||
return fmt.Errorf("dhcpv6: interface %s: %w", ifaceName, err)
|
||||
}
|
||||
|
||||
switch len(dnsIPAddrs) {
|
||||
case 0:
|
||||
log.Debug("dhcpv6: no ipv6 address for interface %s", iface.Name)
|
||||
|
||||
if len(dnsIPAddrs) == 0 {
|
||||
// No available IP addresses which may appear later.
|
||||
return nil
|
||||
case 1:
|
||||
// See the comment in (*v4Server).Start.
|
||||
log.Debug("dhcpv6: setting secondary dns ip to iself for interface %s", iface.Name)
|
||||
dnsIPAddrs = append(dnsIPAddrs, dnsIPAddrs[0])
|
||||
default:
|
||||
// Go on.
|
||||
}
|
||||
|
||||
s.conf.dnsIPAddrs = dnsIPAddrs
|
||||
@@ -624,7 +597,7 @@ 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)
|
||||
@@ -655,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 {
|
||||
@@ -663,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
|
||||
}
|
||||
|
||||
@@ -146,6 +146,8 @@ const (
|
||||
FilteredSafeSearch
|
||||
// FilteredBlockedService - the host is blocked by "blocked services" settings
|
||||
FilteredBlockedService
|
||||
// FilteredRebind - the request was blocked due to DNS rebinding protection
|
||||
FilteredRebind
|
||||
|
||||
// ReasonRewrite - rewrite rule was applied
|
||||
ReasonRewrite
|
||||
@@ -165,6 +167,7 @@ var reasonNames = []string{
|
||||
"FilteredInvalid",
|
||||
"FilteredSafeSearch",
|
||||
"FilteredBlockedService",
|
||||
"FilteredRebind",
|
||||
|
||||
"Rewrite",
|
||||
"RewriteEtcHosts",
|
||||
@@ -177,6 +180,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{}
|
||||
@@ -253,11 +266,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,9 +348,9 @@ 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 {
|
||||
matched, err := d.checkAutoHosts(host, qtype, &result)
|
||||
matched := d.checkAutoHosts(host, qtype, &result)
|
||||
if matched {
|
||||
return result, err
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,13 +415,13 @@ 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, err error) {
|
||||
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, nil
|
||||
return true
|
||||
}
|
||||
|
||||
revHosts := d.Config.AutoHosts.ProcessReverse(host, qtype)
|
||||
@@ -412,10 +434,10 @@ func (d *Dnsfilter) checkAutoHosts(host string, qtype uint16, result *Result) (m
|
||||
result.ReverseHosts[i] = revHosts[i] + "."
|
||||
}
|
||||
|
||||
return true, nil
|
||||
return true
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return false
|
||||
}
|
||||
|
||||
// Process rewrites table
|
||||
@@ -586,11 +608,13 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16, setts RequestFilteringS
|
||||
// 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)
|
||||
|
||||
@@ -368,6 +368,7 @@ const (
|
||||
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 {
|
||||
@@ -376,44 +377,51 @@ 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) {
|
||||
@@ -425,7 +433,7 @@ func TestMatching(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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,148 +0,0 @@
|
||||
package dnsfilter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
/*
|
||||
expire byte[4]
|
||||
res Result
|
||||
*/
|
||||
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
||||
var buf bytes.Buffer
|
||||
|
||||
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
||||
exp := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(exp, uint32(expire))
|
||||
_, _ = buf.Write(exp)
|
||||
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err := enc.Encode(res)
|
||||
if err != nil {
|
||||
log.Error("gob.Encode(): %s", err)
|
||||
return 0
|
||||
}
|
||||
val := buf.Bytes()
|
||||
_ = cache.Set([]byte(host), val)
|
||||
return len(val)
|
||||
}
|
||||
|
||||
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
||||
data := cache.Get([]byte(host))
|
||||
if data == nil {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
exp := int(binary.BigEndian.Uint32(data[:4]))
|
||||
if exp <= int(time.Now().Unix()) {
|
||||
cache.Del([]byte(host))
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Write(data[4:])
|
||||
dec := gob.NewDecoder(&buf)
|
||||
r := Result{}
|
||||
err := dec.Decode(&r)
|
||||
if err != nil {
|
||||
log.Debug("gob.Decode(): %s", err)
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
return r, true
|
||||
}
|
||||
|
||||
// SafeSearchDomain returns replacement address for search engine
|
||||
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
||||
val, ok := safeSearchDomains[host]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
||||
}
|
||||
|
||||
// Check cache. Return cached result if it was found
|
||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
|
||||
if isFound {
|
||||
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
||||
log.Tracef("SafeSearch: found in cache: %s", host)
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
safeHost, ok := d.SafeSearchDomain(host)
|
||||
if !ok {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
||||
if ip := net.ParseIP(safeHost); ip != nil {
|
||||
res.IP = ip
|
||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TODO this address should be resolved with upstream that was configured in dnsforward
|
||||
addrs, err := net.LookupIP(safeHost)
|
||||
if err != nil {
|
||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
for _, i := range addrs {
|
||||
if ipv4 := i.To4(); ipv4 != nil {
|
||||
res.IP = ipv4
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(res.IP) == 0 {
|
||||
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
||||
}
|
||||
|
||||
// Cache result
|
||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
||||
d.Config.SafeSearchEnabled = true
|
||||
d.Config.ConfigModified()
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
||||
d.Config.SafeSearchEnabled = false
|
||||
d.Config.ConfigModified()
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"enabled": d.Config.SafeSearchEnabled,
|
||||
}
|
||||
jsonVal, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,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",
|
||||
|
||||
@@ -71,31 +71,35 @@ func (c *sbCtx) setCache(prefix, 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,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{}) {
|
||||
@@ -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^",
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
)
|
||||
|
||||
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
|
||||
@@ -75,6 +76,11 @@ type FilteringConfig struct {
|
||||
CacheMinTTL uint32 `yaml:"cache_ttl_min"` // override TTL value (minimum) received from upstream server
|
||||
CacheMaxTTL uint32 `yaml:"cache_ttl_max"` // override TTL value (maximum) received from upstream server
|
||||
|
||||
// DNS rebinding protection settings
|
||||
// --
|
||||
RebindingProtectionEnabled bool `yaml:"rebinding_protection_enabled"`
|
||||
RebindingAllowedHosts []string `yaml:"rebinding_allowed_hosts"`
|
||||
|
||||
// Other settings
|
||||
// --
|
||||
|
||||
@@ -94,19 +100,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.
|
||||
@@ -119,6 +139,7 @@ type ServerConfig struct {
|
||||
|
||||
FilteringConfig
|
||||
TLSConfig
|
||||
DNSCryptConfig
|
||||
TLSAllowUnencryptedDOH bool
|
||||
|
||||
TLSv12Roots *x509.CertPool // list of root CAs for TLSv1.2
|
||||
@@ -184,6 +205,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")
|
||||
|
||||
@@ -48,6 +48,7 @@ func (s *Server) handleDNSRequest(_ *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
processFilteringBeforeRequest,
|
||||
processUpstream,
|
||||
processDNSSECAfterResponse,
|
||||
processRebindingFilteringAfterResponse,
|
||||
processFilteringAfterResponse,
|
||||
s.ipset.process,
|
||||
processQueryLogsAndStats,
|
||||
@@ -53,6 +53,7 @@ type Server struct {
|
||||
queryLog querylog.QueryLog // Query log instance
|
||||
stats stats.Stats
|
||||
access *accessCtx
|
||||
rebinding *dnsRebindChecker
|
||||
|
||||
ipset ipsetCtx
|
||||
|
||||
@@ -122,6 +123,7 @@ func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
||||
c.DisallowedClients = stringArrayDup(sc.DisallowedClients)
|
||||
c.BlockedHosts = stringArrayDup(sc.BlockedHosts)
|
||||
c.UpstreamDNS = stringArrayDup(sc.UpstreamDNS)
|
||||
c.RebindingAllowedHosts = stringArrayDup(sc.RebindingAllowedHosts)
|
||||
s.RUnlock()
|
||||
}
|
||||
|
||||
@@ -221,6 +223,13 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize DNS rebinding module
|
||||
// --
|
||||
s.rebinding, err = newRebindChecker(s.conf.RebindingAllowedHosts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register web handlers if necessary
|
||||
// --
|
||||
if !webRegistered && s.conf.HTTPRegister != nil {
|
||||
|
||||
@@ -738,6 +738,100 @@ func TestRewrite(t *testing.T) {
|
||||
_ = s.Stop()
|
||||
}
|
||||
|
||||
func TestBlockedDNSRebinding(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
|
||||
//
|
||||
// DNS rebinding protection
|
||||
//
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
req.Question = []dns.Question{
|
||||
{Name: "192-168-1-250.nip.io.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
s.conf.RebindingProtectionEnabled = true
|
||||
s.Unlock()
|
||||
|
||||
reply, err := dns.Exchange(&req, addr.String())
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't talk to server %s: %s", addr, err)
|
||||
}
|
||||
|
||||
if len(reply.Answer) != 1 {
|
||||
t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer))
|
||||
}
|
||||
|
||||
a, ok := reply.Answer[0].(*dns.A)
|
||||
if !ok {
|
||||
t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0])
|
||||
}
|
||||
|
||||
if !net.IPv4zero.Equal(a.A) {
|
||||
t.Fatalf("DNS server %s returned wrong answer instead of 0.0.0.0: %v", addr, a.A)
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
s.conf.RebindingProtectionEnabled = false
|
||||
s.Unlock()
|
||||
|
||||
reply, err = dns.Exchange(&req, addr.String())
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't talk to server %s: %s", addr, err)
|
||||
}
|
||||
|
||||
if len(reply.Answer) != 1 {
|
||||
t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer))
|
||||
}
|
||||
|
||||
a, ok = reply.Answer[0].(*dns.A)
|
||||
if !ok {
|
||||
t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0])
|
||||
}
|
||||
|
||||
if !net.IPv4(192, 168, 1, 250).Equal(a.A) {
|
||||
t.Fatalf("DNS server %s returned wrong answer instead of 192.168.1.250: %v", addr, a.A)
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
s.conf.RebindingProtectionEnabled = true
|
||||
s.rebinding, _ = newRebindChecker([]string{
|
||||
"||nip.io^",
|
||||
})
|
||||
s.Unlock()
|
||||
|
||||
reply, err = dns.Exchange(&req, addr.String())
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't talk to server %s: %s", addr, err)
|
||||
}
|
||||
|
||||
if len(reply.Answer) != 1 {
|
||||
t.Fatalf("DNS server %s returned reply with wrong number of answers - %d", addr, len(reply.Answer))
|
||||
}
|
||||
|
||||
a, ok = reply.Answer[0].(*dns.A)
|
||||
if !ok {
|
||||
t.Fatalf("DNS server %s returned wrong answer type instead of A: %v", addr, reply.Answer[0])
|
||||
}
|
||||
|
||||
if !net.IPv4(192, 168, 1, 250).Equal(a.A) {
|
||||
t.Fatalf("DNS server %s returned wrong answer instead of 192.168.1.250: %v", addr, a.A)
|
||||
}
|
||||
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("DNS server failed to stop: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func createTestServer(t *testing.T) *Server {
|
||||
rules := `||nxdomain.example.org
|
||||
||null.example.org^
|
||||
|
||||
@@ -113,8 +113,8 @@ func (s *Server) filterDNSResponse(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
|
||||
switch v := a.(type) {
|
||||
case *dns.CNAME:
|
||||
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
||||
host = strings.TrimSuffix(v.Target, ".")
|
||||
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
||||
|
||||
case *dns.A:
|
||||
host = v.A.String()
|
||||
|
||||
@@ -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"
|
||||
@@ -21,232 +20,301 @@ func httpError(r *http.Request, w http.ResponseWriter, code int, format string,
|
||||
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"`
|
||||
|
||||
RebindingProtectionEnabled *bool `json:"rebinding_protection_enabled"`
|
||||
RebindingAllowedHosts *[]string `json:"rebinding_allowed_hosts"`
|
||||
}
|
||||
|
||||
func (s *Server) getDNSConfig() dnsConfig {
|
||||
s.RLock()
|
||||
upstreams := stringArrayDup(s.conf.UpstreamDNS)
|
||||
upstreamFile := s.conf.UpstreamDNSFileName
|
||||
bootstraps := stringArrayDup(s.conf.BootstrapDNS)
|
||||
protectionEnabled := s.conf.ProtectionEnabled
|
||||
blockingMode := s.conf.BlockingMode
|
||||
BlockingIPv4 := s.conf.BlockingIPv4
|
||||
BlockingIPv6 := s.conf.BlockingIPv6
|
||||
Ratelimit := s.conf.Ratelimit
|
||||
EnableEDNSClientSubnet := s.conf.EnableEDNSClientSubnet
|
||||
EnableDNSSEC := s.conf.EnableDNSSEC
|
||||
AAAADisabled := s.conf.AAAADisabled
|
||||
CacheSize := s.conf.CacheSize
|
||||
CacheMinTTL := s.conf.CacheMinTTL
|
||||
CacheMaxTTL := s.conf.CacheMaxTTL
|
||||
var upstreamMode string
|
||||
if s.conf.FastestAddr {
|
||||
upstreamMode = "fastest_addr"
|
||||
} else if s.conf.AllServers {
|
||||
upstreamMode = "parallel"
|
||||
}
|
||||
rebindingEnabled := s.conf.RebindingProtectionEnabled
|
||||
rebindingAllowedHosts := stringArrayDup(s.conf.RebindingAllowedHosts)
|
||||
s.RUnlock()
|
||||
return dnsConfig{
|
||||
Upstreams: &upstreams,
|
||||
UpstreamsFile: &upstreamFile,
|
||||
Bootstraps: &bootstraps,
|
||||
ProtectionEnabled: &protectionEnabled,
|
||||
BlockingMode: &blockingMode,
|
||||
BlockingIPv4: &BlockingIPv4,
|
||||
BlockingIPv6: &BlockingIPv6,
|
||||
RateLimit: &Ratelimit,
|
||||
EDNSCSEnabled: &EnableEDNSClientSubnet,
|
||||
DNSSECEnabled: &EnableDNSSEC,
|
||||
DisableIPv6: &AAAADisabled,
|
||||
CacheSize: &CacheSize,
|
||||
CacheMinTTL: &CacheMinTTL,
|
||||
CacheMaxTTL: &CacheMaxTTL,
|
||||
UpstreamMode: &upstreamMode,
|
||||
RebindingProtectionEnabled: &rebindingEnabled,
|
||||
RebindingAllowedHosts: &rebindingAllowedHosts,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
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: %w", 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
|
||||
}
|
||||
}
|
||||
|
||||
restart = restart || s.setRebindingConfig(dc)
|
||||
s.Unlock()
|
||||
s.conf.ConfigModified()
|
||||
return restart
|
||||
}
|
||||
|
||||
type upstreamJSON struct {
|
||||
Upstreams []string `json:"upstream_dns"` // Upstreams
|
||||
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
|
||||
@@ -29,7 +29,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||
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}",
|
||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "fastest_addr",
|
||||
conf: func() ServerConfig {
|
||||
@@ -37,7 +37,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||
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}",
|
||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "parallel",
|
||||
conf: func() ServerConfig {
|
||||
@@ -45,7 +45,7 @@ func TestDNSForwardHTTTP_handleGetConfig(t *testing.T) {
|
||||
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}",
|
||||
want: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -73,7 +73,7 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}"
|
||||
const defaultConfJSON = "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n"
|
||||
testCases := []struct {
|
||||
name string
|
||||
req string
|
||||
@@ -83,52 +83,52 @@ func TestDNSForwardHTTTP_handleSetConfig(t *testing.T) {
|
||||
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}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:77\",\"8.8.4.4:77\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "bootstraps",
|
||||
req: "{\"bootstrap_dns\":[\"9.9.9.10\"]}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "blocking_mode_good",
|
||||
req: "{\"blocking_mode\":\"refused\"}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"refused\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "blocking_mode_bad",
|
||||
req: "{\"blocking_mode\":\"custom_ip\"}",
|
||||
wantSet: "blocking_mode: incorrect value\n",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "ratelimit",
|
||||
req: "{\"ratelimit\":6}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":6,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "edns_cs_enabled",
|
||||
req: "{\"edns_cs_enabled\":true}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":true,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "dnssec_enabled",
|
||||
req: "{\"dnssec_enabled\":true}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":true,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "cache_size",
|
||||
req: "{\"cache_size\":1024}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"\",\"cache_size\":1024,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "upstream_mode_parallel",
|
||||
req: "{\"upstream_mode\":\"parallel\"}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"parallel\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "upstream_mode_fastest_addr",
|
||||
req: "{\"upstream_mode\":\"fastest_addr\"}",
|
||||
wantSet: "",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0}",
|
||||
wantGet: "{\"upstream_dns\":[\"8.8.8.8:53\",\"8.8.4.4:53\"],\"upstream_dns_file\":\"\",\"bootstrap_dns\":[\"9.9.9.10\",\"149.112.112.10\",\"2620:fe::10\",\"2620:fe::fe:10\"],\"protection_enabled\":true,\"ratelimit\":0,\"blocking_mode\":\"\",\"blocking_ipv4\":\"\",\"blocking_ipv6\":\"\",\"edns_cs_enabled\":false,\"dnssec_enabled\":false,\"disable_ipv6\":false,\"upstream_mode\":\"fastest_addr\",\"cache_size\":0,\"cache_ttl_min\":0,\"cache_ttl_max\":0,\"rebinding_protection_enabled\":false,\"rebinding_allowed_hosts\":[]}\n",
|
||||
}, {
|
||||
name: "upstream_dns_bad",
|
||||
req: "{\"upstream_dns\":[\"\"]}",
|
||||
@@ -1,12 +1,12 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
|
||||
227
internal/dnsforward/rebind.go
Normal file
227
internal/dnsforward/rebind.go
Normal file
@@ -0,0 +1,227 @@
|
||||
// DNS Rebinding protection
|
||||
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type dnsRebindChecker struct {
|
||||
allowDomainEngine *urlfilter.DNSEngine
|
||||
}
|
||||
|
||||
func newRebindChecker(allowedHosts []string) (*dnsRebindChecker, error) {
|
||||
buf := strings.Builder{}
|
||||
for _, s := range allowedHosts {
|
||||
buf.WriteString(s)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
rulesStorage, err := filterlist.NewRuleStorage([]filterlist.RuleList{
|
||||
&filterlist.StringRuleList{
|
||||
ID: int(0),
|
||||
RulesText: buf.String(),
|
||||
IgnoreCosmetic: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dnsRebindChecker{
|
||||
allowDomainEngine: urlfilter.NewDNSEngine(rulesStorage),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *dnsRebindChecker) isAllowedDomain(domain string) bool {
|
||||
_, ok := c.allowDomainEngine.Match(domain)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsPrivate reports whether ip is a private address, according to
|
||||
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
|
||||
func (*dnsRebindChecker) isPrivate(ip net.IP) bool {
|
||||
//TODO: remove once https://github.com/golang/go/pull/42793 makes it to stdlib
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4[0] == 10 ||
|
||||
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
|
||||
(ip4[0] == 192 && ip4[1] == 168)
|
||||
}
|
||||
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
|
||||
}
|
||||
|
||||
func (c *dnsRebindChecker) isRebindHost(host string) bool {
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
return c.isRebindIP(ip)
|
||||
}
|
||||
|
||||
return host == "localhost"
|
||||
}
|
||||
|
||||
func (c *dnsRebindChecker) isLocalNetworkV4(ip4 net.IP) bool {
|
||||
switch {
|
||||
case ip4[0] == 0:
|
||||
/* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
|
||||
case ip4[0] == 10:
|
||||
/* 10.0.0.0/8 (private) */
|
||||
case ip4[0] == 172 && ip4[1]&0x10 == 0x10:
|
||||
/* 172.16.0.0/12 (private) */
|
||||
case ip4[0] == 169 && ip4[1] == 254:
|
||||
/* 169.254.0.0/16 (zeroconf) */
|
||||
case ip4[0] == 192 && ip4[1] == 0 && ip4[2] == 2:
|
||||
/* 192.0.2.0/24 (test-net) */
|
||||
case ip4[0] == 198 && ip4[1] == 51 && ip4[2] == 100:
|
||||
/* 198.51.100.0/24(test-net) */
|
||||
case ip4[0] == 203 && ip4[1] == 0 && ip4[2] == 113:
|
||||
/* 203.0.113.0/24 (test-net) */
|
||||
case ip4.Equal(net.IPv4bcast):
|
||||
/* 255.255.255.255/32 (broadcast)*/
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *dnsRebindChecker) isLocalNetworkV6(ip6 net.IP) bool {
|
||||
return ip6.Equal(net.IPv6zero) ||
|
||||
ip6.Equal(net.IPv6unspecified) ||
|
||||
ip6.Equal(net.IPv6interfacelocalallnodes) ||
|
||||
ip6.Equal(net.IPv6linklocalallnodes) ||
|
||||
ip6.Equal(net.IPv6linklocalallrouters)
|
||||
}
|
||||
|
||||
func (c *dnsRebindChecker) isRebindIP(ip net.IP) bool {
|
||||
// This is compatible with dnsmasq definition
|
||||
// See: https://github.com/imp/dnsmasq/blob/4e7694d7107d2299f4aaededf8917fceb5dfb924/src/rfc1035.c#L412
|
||||
|
||||
rebind := false
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
rebind = c.isLocalNetworkV4(ip4)
|
||||
} else {
|
||||
rebind = c.isLocalNetworkV6(ip)
|
||||
}
|
||||
|
||||
return rebind || c.isPrivate(ip) || ip.IsLoopback()
|
||||
}
|
||||
|
||||
// Checks DNS rebinding attacks
|
||||
// Note both whitelisted and cached hosts will bypass rebinding check (see: processFilteringAfterResponse()).
|
||||
func (s *Server) isResponseRebind(domain, host string) bool {
|
||||
if !s.conf.RebindingProtectionEnabled {
|
||||
return false
|
||||
}
|
||||
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("DNS Rebinding check for %s -> %s", domain, host)
|
||||
}
|
||||
|
||||
if s.rebinding.isAllowedDomain(domain) {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.rebinding.isRebindHost(host)
|
||||
}
|
||||
|
||||
func processRebindingFilteringAfterResponse(ctx *dnsContext) int {
|
||||
s := ctx.srv
|
||||
d := ctx.proxyCtx
|
||||
res := ctx.result
|
||||
var err error
|
||||
|
||||
if !ctx.responseFromUpstream || res.Reason == dnsfilter.ReasonRewrite {
|
||||
return resultDone
|
||||
}
|
||||
|
||||
originalRes := d.Res
|
||||
ctx.result, err = s.preventRebindResponse(ctx)
|
||||
if err != nil {
|
||||
ctx.err = err
|
||||
return resultError
|
||||
}
|
||||
if ctx.result != nil {
|
||||
ctx.origResp = originalRes // matched by response
|
||||
} else {
|
||||
ctx.result = &dnsfilter.Result{}
|
||||
}
|
||||
|
||||
return resultDone
|
||||
}
|
||||
|
||||
func (s *Server) setRebindingConfig(dc dnsConfig) bool {
|
||||
restart := false
|
||||
|
||||
if dc.RebindingProtectionEnabled != nil {
|
||||
s.conf.RebindingProtectionEnabled = *dc.RebindingProtectionEnabled
|
||||
}
|
||||
|
||||
if dc.RebindingAllowedHosts != nil {
|
||||
s.conf.RebindingAllowedHosts = *dc.RebindingAllowedHosts
|
||||
restart = true
|
||||
}
|
||||
|
||||
return restart
|
||||
}
|
||||
|
||||
func (s *Server) preventRebindResponse(ctx *dnsContext) (*dnsfilter.Result, error) {
|
||||
d := ctx.proxyCtx
|
||||
|
||||
for _, a := range d.Res.Answer {
|
||||
m := ""
|
||||
domainName := ""
|
||||
host := ""
|
||||
|
||||
switch v := a.(type) {
|
||||
case *dns.CNAME:
|
||||
host = strings.TrimSuffix(v.Target, ".")
|
||||
domainName = v.Hdr.Name
|
||||
m = fmt.Sprintf("DNSRebind: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
||||
|
||||
case *dns.A:
|
||||
host = v.A.String()
|
||||
domainName = v.Hdr.Name
|
||||
m = fmt.Sprintf("DNSRebind: Checking record A (%s) for %s", host, v.Hdr.Name)
|
||||
|
||||
case *dns.AAAA:
|
||||
host = v.AAAA.String()
|
||||
domainName = v.Hdr.Name
|
||||
m = fmt.Sprintf("DNSRebind: Checking record AAAA (%s) for %s", host, v.Hdr.Name)
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
s.RLock()
|
||||
if !s.conf.RebindingProtectionEnabled {
|
||||
s.RUnlock()
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debug(m)
|
||||
blocked := s.isResponseRebind(strings.TrimSuffix(domainName, "."), host)
|
||||
s.RUnlock()
|
||||
|
||||
if blocked {
|
||||
res := &dnsfilter.Result{
|
||||
IsFiltered: true,
|
||||
Reason: dnsfilter.FilteredRebind,
|
||||
Rule: "adguard-rebind-protection",
|
||||
}
|
||||
|
||||
d.Res = s.genDNSFilterMessage(d, res)
|
||||
log.Debug("DNSRebind: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
113
internal/dnsforward/rebind_test.go
Normal file
113
internal/dnsforward/rebind_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRebindingPrivateAddresses(t *testing.T) {
|
||||
c, _ := newRebindChecker(nil)
|
||||
|
||||
r1 := byte(rand.Int31() & 0xFE)
|
||||
r2 := byte(rand.Int31() & 0xFE)
|
||||
r3 := byte(rand.Int31() & 0xFE)
|
||||
|
||||
for _, ip := range []net.IP{
|
||||
net.IPv4(0, r1, r2, r3), /* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
|
||||
net.IPv4(127, r1, r2, r3), /* 127.0.0.0/8 (loopback) */
|
||||
net.IPv4(10, r1, r2, r3), /* 10.0.0.0/8 (private) */
|
||||
net.IPv4(172, 0x10|byte(1&rand.Int31()), r2, r3), /* 172.16.0.0/12 (private) */
|
||||
net.IPv4(192, 168, r2, r3), /* 192.168.0.0/16 (private) */
|
||||
net.IPv4(169, 254, r2, r3), /* 169.254.0.0/16 (zeroconf) */
|
||||
net.IPv4(192, 0, 2, r3), /* 192.0.2.0/24 (test-net) */
|
||||
net.IPv4(198, 51, 100, r3), /* 198.51.100.0/24(test-net) */
|
||||
net.IPv4(203, 0, 113, r3), /* 203.0.113.0/24 (test-net) */
|
||||
net.IPv4(255, 255, 255, 255), /* 255.255.255.255/32 (broadcast)*/
|
||||
|
||||
/* RFC 6303 4.3 (unspecified & loopback) */
|
||||
net.IPv6zero,
|
||||
net.IPv6unspecified,
|
||||
|
||||
/* RFC 6303 4.4 */
|
||||
/* RFC 6303 4.5 */
|
||||
/* RFC 6303 4.6 */
|
||||
net.IPv6interfacelocalallnodes,
|
||||
net.IPv6linklocalallnodes,
|
||||
net.IPv6linklocalallrouters,
|
||||
|
||||
/* (TODO) Check IPv4-mapped IPv6 addresses */
|
||||
} {
|
||||
assert.Truef(t, c.isRebindIP(ip), "%s is not a rebind", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebindLocalhost(t *testing.T) {
|
||||
c := &dnsRebindChecker{}
|
||||
assert.False(t, c.isRebindHost("example.com"))
|
||||
assert.False(t, c.isRebindHost("200.0.0.1"))
|
||||
assert.True(t, c.isRebindHost("127.0.0.1"))
|
||||
assert.True(t, c.isRebindHost("localhost"))
|
||||
}
|
||||
|
||||
func TestIsResponseRebind(t *testing.T) {
|
||||
c, _ := newRebindChecker([]string{
|
||||
"||totally-safe.com^",
|
||||
})
|
||||
s := &Server{
|
||||
rebinding: c,
|
||||
}
|
||||
|
||||
for _, host := range []string{
|
||||
"0.1.2.3", /* 0.0.0.0/8 (RFC 5735 section 3. "here" network) */
|
||||
"127.1.2.3", /* 127.0.0.0/8 (loopback) */
|
||||
"10.1.2.3", /* 10.0.0.0/8 (private) */
|
||||
"172.16.2.3", /* 172.16.0.0/12 (private) */
|
||||
"192.168.2.3", /* 192.168.0.0/16 (private) */
|
||||
"169.254.2.3", /* 169.254.0.0/16 (zeroconf) */
|
||||
"192.0.2.3", /* 192.0.2.0/24 (test-net) */
|
||||
"198.51.100.3", /* 198.51.100.0/24(test-net) */
|
||||
"203.0.113.3", /* 203.0.113.0/24 (test-net) */
|
||||
"255.255.255.255", /* 255.255.255.255/32 (broadcast)*/
|
||||
|
||||
/* RFC 6303 4.3 (unspecified & loopback) */
|
||||
net.IPv6zero.String(),
|
||||
net.IPv6unspecified.String(),
|
||||
|
||||
/* RFC 6303 4.4 */
|
||||
/* RFC 6303 4.5 */
|
||||
/* RFC 6303 4.6 */
|
||||
net.IPv6interfacelocalallnodes.String(),
|
||||
net.IPv6linklocalallnodes.String(),
|
||||
net.IPv6linklocalallrouters.String(),
|
||||
|
||||
"localhost",
|
||||
} {
|
||||
s.conf.RebindingProtectionEnabled = true
|
||||
assert.Truef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("absolutely.totally-safe.com", host), "host: %s", host)
|
||||
|
||||
s.conf.RebindingProtectionEnabled = false
|
||||
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("absolutely.totally-safe.com", host), "host: %s", host)
|
||||
}
|
||||
|
||||
for _, host := range []string{
|
||||
"200.168.2.3",
|
||||
"another-example.com",
|
||||
} {
|
||||
s.conf.RebindingProtectionEnabled = true
|
||||
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("absolutely.totally-legit.com", host), "host: %s", host)
|
||||
|
||||
s.conf.RebindingProtectionEnabled = false
|
||||
assert.Falsef(t, s.isResponseRebind("example.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("totally-safe.com", host), "host: %s", host)
|
||||
assert.Falsef(t, s.isResponseRebind("absolutely.totally-legit.com", host), "host: %s", host)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -76,7 +78,6 @@ func InitAuth(dbFilename string, users []User, sessionTTL uint32) *Auth {
|
||||
a := Auth{}
|
||||
a.sessionTTL = sessionTTL
|
||||
a.sessions = make(map[string]*session)
|
||||
rand.Seed(time.Now().UTC().Unix())
|
||||
var err error
|
||||
a.db, err = bbolt.Open(dbFilename, 0o644, nil)
|
||||
if err != nil {
|
||||
@@ -275,23 +276,28 @@ type loginJSON struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func getSession(u *User) []byte {
|
||||
// the developers don't currently believe that using a
|
||||
// non-cryptographic RNG for the session hash salt is
|
||||
// insecure
|
||||
salt := rand.Uint32() //nolint:gosec
|
||||
d := []byte(fmt.Sprintf("%d%s%s", salt, u.Name, u.PasswordHash))
|
||||
hash := sha256.Sum256(d)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func (a *Auth) httpCookie(req loginJSON) string {
|
||||
u := a.UserFind(req.Name, req.Password)
|
||||
if len(u.Name) == 0 {
|
||||
return ""
|
||||
func getSession(u *User) ([]byte, error) {
|
||||
maxSalt := big.NewInt(math.MaxUint32)
|
||||
salt, err := rand.Int(rand.Reader, maxSalt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess := getSession(&u)
|
||||
d := []byte(fmt.Sprintf("%s%s%s", salt, u.Name, u.PasswordHash))
|
||||
hash := sha256.Sum256(d)
|
||||
return hash[:], nil
|
||||
}
|
||||
|
||||
func (a *Auth) httpCookie(req loginJSON) (string, error) {
|
||||
u := a.UserFind(req.Name, req.Password)
|
||||
if len(u.Name) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
sess, err := getSession(&u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
expire := now.Add(cookieTTL * time.Hour)
|
||||
@@ -305,7 +311,7 @@ func (a *Auth) httpCookie(req loginJSON) string {
|
||||
a.addSession(sess, &s)
|
||||
|
||||
return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s",
|
||||
sessionCookieName, hex.EncodeToString(sess), expstr)
|
||||
sessionCookieName, hex.EncodeToString(sess), expstr), nil
|
||||
}
|
||||
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -316,7 +322,11 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
cookie := Context.auth.httpCookie(req)
|
||||
cookie, err := Context.auth.httpCookie(req)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "crypto rand reader: %s", err)
|
||||
return
|
||||
}
|
||||
if len(cookie) == 0 {
|
||||
log.Info("Auth: invalid user name or password: name=%q", req.Name)
|
||||
time.Sleep(1 * time.Second)
|
||||
@@ -350,7 +360,7 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// RegisterAuthHandlers - register handlers
|
||||
func RegisterAuthHandlers() {
|
||||
http.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin)))
|
||||
Context.mux.Handle("/control/login", postInstallHandler(ensureHandler("POST", handleLogin)))
|
||||
httpRegister("GET", "/control/logout", handleLogout)
|
||||
}
|
||||
|
||||
@@ -369,7 +379,54 @@ func parseCookie(cookie string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// nolint(gocyclo)
|
||||
// optionalAuthThird return true if user should authenticate first.
|
||||
func optionalAuthThird(w http.ResponseWriter, r *http.Request) (authFirst bool) {
|
||||
authFirst = false
|
||||
|
||||
// redirect to login page if not authenticated
|
||||
ok := false
|
||||
cookie, err := r.Cookie(sessionCookieName)
|
||||
|
||||
if glProcessCookie(r) {
|
||||
log.Debug("Auth: authentification was handled by GL-Inet submodule")
|
||||
ok = true
|
||||
|
||||
} else if err == nil {
|
||||
r := Context.auth.CheckSession(cookie.Value)
|
||||
if r == 0 {
|
||||
ok = true
|
||||
} else if r < 0 {
|
||||
log.Debug("Auth: invalid cookie value: %s", cookie)
|
||||
}
|
||||
} else {
|
||||
// there's no Cookie, check Basic authentication
|
||||
user, pass, ok2 := r.BasicAuth()
|
||||
if ok2 {
|
||||
u := Context.auth.UserFind(user, pass)
|
||||
if len(u.Name) != 0 {
|
||||
ok = true
|
||||
} else {
|
||||
log.Info("Auth: invalid Basic Authorization value")
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
if glProcessRedirect(w, r) {
|
||||
log.Debug("Auth: redirected to login page by GL-Inet submodule")
|
||||
} else {
|
||||
w.Header().Set("Location", "/login.html")
|
||||
w.WriteHeader(http.StatusFound)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, _ = w.Write([]byte("Forbidden"))
|
||||
}
|
||||
authFirst = true
|
||||
}
|
||||
return authFirst
|
||||
}
|
||||
|
||||
func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/login.html" {
|
||||
@@ -392,45 +449,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||
// process as usual
|
||||
// no additional auth requirements
|
||||
} else if Context.auth != nil && Context.auth.AuthRequired() {
|
||||
// redirect to login page if not authenticated
|
||||
ok := false
|
||||
cookie, err := r.Cookie(sessionCookieName)
|
||||
|
||||
if glProcessCookie(r) {
|
||||
log.Debug("Auth: authentification was handled by GL-Inet submodule")
|
||||
ok = true
|
||||
|
||||
} else if err == nil {
|
||||
r := Context.auth.CheckSession(cookie.Value)
|
||||
if r == 0 {
|
||||
ok = true
|
||||
} else if r < 0 {
|
||||
log.Debug("Auth: invalid cookie value: %s", cookie)
|
||||
}
|
||||
} else {
|
||||
// there's no Cookie, check Basic authentication
|
||||
user, pass, ok2 := r.BasicAuth()
|
||||
if ok2 {
|
||||
u := Context.auth.UserFind(user, pass)
|
||||
if len(u.Name) != 0 {
|
||||
ok = true
|
||||
} else {
|
||||
log.Info("Auth: invalid Basic Authorization value")
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
|
||||
if glProcessRedirect(w, r) {
|
||||
log.Debug("Auth: redirected to login page by GL-Inet submodule")
|
||||
} else {
|
||||
w.Header().Set("Location", "/login.html")
|
||||
w.WriteHeader(http.StatusFound)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, _ = w.Write([]byte("Forbidden"))
|
||||
}
|
||||
if optionalAuthThird(w, r) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestMain(m *testing.M) {
|
||||
func prepareTestDir() string {
|
||||
const dir = "./agh-test"
|
||||
_ = os.RemoveAll(dir)
|
||||
_ = os.MkdirAll(dir, 0755)
|
||||
_ = os.MkdirAll(dir, 0o755)
|
||||
return dir
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestAuth(t *testing.T) {
|
||||
fn := filepath.Join(dir, "sessions.db")
|
||||
|
||||
users := []User{
|
||||
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||
}
|
||||
a := InitAuth(fn, nil, 60)
|
||||
s := session{}
|
||||
@@ -41,7 +41,8 @@ func TestAuth(t *testing.T) {
|
||||
assert.True(t, a.CheckSession("notfound") == -1)
|
||||
a.RemoveSession("notfound")
|
||||
|
||||
sess := getSession(&users[0])
|
||||
sess, err := getSession(&users[0])
|
||||
assert.Nil(t, err)
|
||||
sessStr := hex.EncodeToString(sess)
|
||||
|
||||
now := time.Now().UTC().Unix()
|
||||
@@ -105,7 +106,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
fn := filepath.Join(dir, "sessions.db")
|
||||
|
||||
users := []User{
|
||||
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||
}
|
||||
Context.auth = InitAuth(fn, users, 60)
|
||||
|
||||
@@ -136,7 +137,8 @@ func TestAuthHTTP(t *testing.T) {
|
||||
assert.True(t, handlerCalled)
|
||||
|
||||
// perform login
|
||||
cookie := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"})
|
||||
cookie, err := Context.auth.httpCookie(loginJSON{Name: "name", Password: "password"})
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, cookie != "")
|
||||
|
||||
// get /
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
@@ -18,8 +19,10 @@ var GLMode bool
|
||||
|
||||
var glFilePrefix = "/tmp/gl_token_"
|
||||
|
||||
const glTokenTimeoutSeconds = 3600
|
||||
const glCookieName = "Admin-Token"
|
||||
const (
|
||||
glTokenTimeoutSeconds = 3600
|
||||
glCookieName = "Admin-Token"
|
||||
)
|
||||
|
||||
func glProcessRedirect(w http.ResponseWriter, r *http.Request) bool {
|
||||
if !GLMode {
|
||||
@@ -71,14 +74,28 @@ func archIsLittleEndian() bool {
|
||||
return (b == 0x04)
|
||||
}
|
||||
|
||||
// MaxFileSize is a maximum file length in bytes.
|
||||
const MaxFileSize = 1024 * 1024
|
||||
|
||||
func glGetTokenDate(file string) uint32 {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
log.Error("os.Open: %s", err)
|
||||
return 0
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fileReadCloser, err := aghio.LimitReadCloser(f, MaxFileSize)
|
||||
if err != nil {
|
||||
log.Error("LimitReadCloser: %s", err)
|
||||
return 0
|
||||
}
|
||||
defer fileReadCloser.Close()
|
||||
|
||||
var dateToken uint32
|
||||
bs, err := ioutil.ReadAll(f)
|
||||
|
||||
// This use of ReadAll is now safe, because we limited reader.
|
||||
bs, err := ioutil.ReadAll(fileReadCloser)
|
||||
if err != nil {
|
||||
log.Error("ioutil.ReadAll: %s", err)
|
||||
return 0
|
||||
@@ -25,7 +25,7 @@ func TestAuthGL(t *testing.T) {
|
||||
} else {
|
||||
binary.BigEndian.PutUint32(data, tval)
|
||||
}
|
||||
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644))
|
||||
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0o644))
|
||||
assert.False(t, glCheckToken("test"))
|
||||
|
||||
tval = uint32(time.Now().UTC().Unix() + 60)
|
||||
@@ -35,7 +35,7 @@ func TestAuthGL(t *testing.T) {
|
||||
} else {
|
||||
binary.BigEndian.PutUint32(data, tval)
|
||||
}
|
||||
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0644))
|
||||
assert.Nil(t, ioutil.WriteFile(glFilePrefix+"test", data, 0o644))
|
||||
r, _ := http.NewRequest("GET", "http://localhost/", nil)
|
||||
r.AddCookie(&http.Cookie{Name: glCookieName, Value: "test"})
|
||||
assert.True(t, glProcessCookie(r))
|
||||
@@ -570,31 +570,35 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) {
|
||||
// so we overwrite existing entries with an equal or higher priority
|
||||
func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (bool, error) {
|
||||
clients.lock.Lock()
|
||||
b, e := clients.addHost(ip, host, source)
|
||||
b := clients.addHost(ip, host, source)
|
||||
clients.lock.Unlock()
|
||||
return b, e
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (clients *clientsContainer) addHost(ip, host string, source clientSource) (bool, error) {
|
||||
// check auto-clients index
|
||||
func (clients *clientsContainer) addHost(ip, host string, source clientSource) (addedNew bool) {
|
||||
ch, ok := clients.ipHost[ip]
|
||||
if ok && ch.Source > source {
|
||||
return false, nil
|
||||
} else if ok {
|
||||
if ok {
|
||||
if ch.Source > source {
|
||||
return false
|
||||
}
|
||||
|
||||
ch.Source = source
|
||||
} else {
|
||||
ch = &ClientHost{
|
||||
Host: host,
|
||||
Source: source,
|
||||
}
|
||||
|
||||
clients.ipHost[ip] = ch
|
||||
}
|
||||
log.Debug("Clients: added %q -> %q [%d]", ip, host, len(clients.ipHost))
|
||||
return true, nil
|
||||
|
||||
log.Debug("clients: added %q -> %q [%d]", ip, host, len(clients.ipHost))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove all entries that match the specified source
|
||||
func (clients *clientsContainer) rmHosts(source clientSource) int {
|
||||
func (clients *clientsContainer) rmHosts(source clientSource) {
|
||||
n := 0
|
||||
for k, v := range clients.ipHost {
|
||||
if v.Source == source {
|
||||
@@ -602,8 +606,8 @@ func (clients *clientsContainer) rmHosts(source clientSource) int {
|
||||
n++
|
||||
}
|
||||
}
|
||||
log.Debug("Clients: removed %d client aliases", n)
|
||||
return n
|
||||
|
||||
log.Debug("clients: removed %d client aliases", n)
|
||||
}
|
||||
|
||||
// addFromHostsFile fills the clients hosts list from the system's hosts files.
|
||||
@@ -613,15 +617,12 @@ func (clients *clientsContainer) addFromHostsFile() {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
_ = clients.rmHosts(ClientSourceHostsFile)
|
||||
clients.rmHosts(ClientSourceHostsFile)
|
||||
|
||||
n := 0
|
||||
for ip, names := range hosts {
|
||||
for _, name := range names {
|
||||
ok, err := clients.addHost(ip, name, ClientSourceHostsFile)
|
||||
if err != nil {
|
||||
log.Debug("Clients: %s", err)
|
||||
}
|
||||
ok := clients.addHost(ip, name, ClientSourceHostsFile)
|
||||
if ok {
|
||||
n++
|
||||
}
|
||||
@@ -650,7 +651,7 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
_ = clients.rmHosts(ClientSourceARP)
|
||||
clients.rmHosts(ClientSourceARP)
|
||||
|
||||
n := 0
|
||||
lines := strings.Split(string(data), "\n")
|
||||
@@ -668,10 +669,7 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||
continue
|
||||
}
|
||||
|
||||
ok, e := clients.addHost(ip, host, ClientSourceARP)
|
||||
if e != nil {
|
||||
log.Tracef("%s", e)
|
||||
}
|
||||
ok := clients.addHost(ip, host, ClientSourceARP)
|
||||
if ok {
|
||||
n++
|
||||
}
|
||||
@@ -689,7 +687,7 @@ func (clients *clientsContainer) addFromDHCP() {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
_ = clients.rmHosts(ClientSourceDHCP)
|
||||
clients.rmHosts(ClientSourceDHCP)
|
||||
|
||||
leases := clients.dhcpServer.Leases(dhcpd.LeasesAll)
|
||||
n := 0
|
||||
@@ -697,7 +695,7 @@ func (clients *clientsContainer) addFromDHCP() {
|
||||
if len(l.Hostname) == 0 {
|
||||
continue
|
||||
}
|
||||
ok, _ := clients.addHost(l.IP.String(), l.Hostname, ClientSourceDHCP)
|
||||
ok := clients.addHost(l.IP.String(), l.Hostname, ClientSourceDHCP)
|
||||
if ok {
|
||||
n++
|
||||
}
|
||||
|
||||
@@ -12,144 +12,155 @@ import (
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
t.Run("add_success", func(t *testing.T) {
|
||||
c := Client{
|
||||
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
||||
Name: "client1",
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
b, err := clients.Add(c)
|
||||
assert.True(t, b)
|
||||
assert.Nil(t, err)
|
||||
|
||||
c, b = clients.Find("1.1.1.1")
|
||||
assert.True(t, b && c.Name == "client1")
|
||||
c = Client{
|
||||
IDs: []string{"2.2.2.2"},
|
||||
Name: "client2",
|
||||
}
|
||||
|
||||
c, b = clients.Find("1:2:3::4")
|
||||
assert.True(t, b && c.Name == "client1")
|
||||
b, err = clients.Add(c)
|
||||
assert.True(t, b)
|
||||
assert.Nil(t, err)
|
||||
|
||||
c, b = clients.Find("2.2.2.2")
|
||||
assert.True(t, b && c.Name == "client2")
|
||||
c, b = clients.Find("1.1.1.1")
|
||||
assert.True(t, b && c.Name == "client1")
|
||||
|
||||
// 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")
|
||||
}
|
||||
c, b = clients.Find("1:2:3::4")
|
||||
assert.True(t, b && c.Name == "client1")
|
||||
|
||||
// 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")
|
||||
}
|
||||
c, b = clients.Find("2.2.2.2")
|
||||
assert.True(t, b && c.Name == "client2")
|
||||
|
||||
// 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))
|
||||
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")
|
||||
}
|
||||
t.Run("add_fail_name", func(t *testing.T) {
|
||||
c := Client{
|
||||
IDs: []string{"1.2.3.5"},
|
||||
Name: "client1",
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
b, err := clients.Add(c)
|
||||
assert.False(t, b)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
// 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")
|
||||
}
|
||||
t.Run("add_fail_ip", func(t *testing.T) {
|
||||
c := Client{
|
||||
IDs: []string{"2.2.2.2"},
|
||||
Name: "client3",
|
||||
}
|
||||
|
||||
// update
|
||||
c.IDs = []string{"1.1.1.2"}
|
||||
c.Name = "client1"
|
||||
if clients.Update("client1", c) != nil {
|
||||
t.Fatalf("Update")
|
||||
}
|
||||
b, err := clients.Add(c)
|
||||
assert.False(t, b)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
// get after update
|
||||
assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||
assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
||||
t.Run("update_fail_name", func(t *testing.T) {
|
||||
c := Client{
|
||||
IDs: []string{"1.2.3.0"},
|
||||
Name: "client3",
|
||||
}
|
||||
|
||||
// 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)
|
||||
err := clients.Update("client3", c)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// failed remove - no such name
|
||||
if clients.Del("client3") {
|
||||
t.Fatalf("Del - no such name")
|
||||
}
|
||||
c = Client{
|
||||
IDs: []string{"1.2.3.0"},
|
||||
Name: "client2",
|
||||
}
|
||||
|
||||
// remove
|
||||
assert.True(t, !(!clients.Del("client1-renamed") || clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
||||
err = clients.Update("client3", c)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
// add host client
|
||||
b, e = clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
||||
if !b || e != nil {
|
||||
t.Fatalf("clientAddHost")
|
||||
}
|
||||
t.Run("update_fail_ip", func(t *testing.T) {
|
||||
c := Client{
|
||||
IDs: []string{"2.2.2.2"},
|
||||
Name: "client1",
|
||||
}
|
||||
|
||||
// failed add - ip exists
|
||||
b, e = clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS)
|
||||
if b || e != nil {
|
||||
t.Fatalf("clientAddHost - ip exists")
|
||||
}
|
||||
err := clients.Update("client1", c)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
|
||||
// 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")
|
||||
}
|
||||
t.Run("update_success", func(t *testing.T) {
|
||||
c := Client{
|
||||
IDs: []string{"1.1.1.2"},
|
||||
Name: "client1",
|
||||
}
|
||||
|
||||
// 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)")
|
||||
}
|
||||
err := clients.Update("client1", c)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// get
|
||||
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||
assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||
assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
||||
|
||||
c = Client{
|
||||
IDs: []string{"1.1.1.2"},
|
||||
Name: "client1-renamed",
|
||||
UseOwnSettings: true,
|
||||
}
|
||||
|
||||
err = clients.Update("client1", c)
|
||||
assert.Nil(t, err)
|
||||
|
||||
c, b := clients.Find("1.1.1.2")
|
||||
assert.True(t, b)
|
||||
assert.True(t, c.Name == "client1-renamed")
|
||||
assert.True(t, c.IDs[0] == "1.1.1.2")
|
||||
assert.True(t, c.UseOwnSettings)
|
||||
assert.Nil(t, clients.list["client1"])
|
||||
})
|
||||
|
||||
t.Run("del_success", func(t *testing.T) {
|
||||
b := clients.Del("client1-renamed")
|
||||
assert.True(t, b)
|
||||
assert.False(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
||||
})
|
||||
|
||||
t.Run("del_fail", func(t *testing.T) {
|
||||
b := clients.Del("client3")
|
||||
assert.False(t, b)
|
||||
})
|
||||
|
||||
t.Run("addhost_success", func(t *testing.T) {
|
||||
b, err := clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
||||
assert.True(t, b)
|
||||
assert.Nil(t, err)
|
||||
|
||||
b, err = clients.AddHost("1.1.1.1", "host2", ClientSourceARP)
|
||||
assert.True(t, b)
|
||||
assert.Nil(t, err)
|
||||
|
||||
b, err = clients.AddHost("1.1.1.1", "host3", ClientSourceHostsFile)
|
||||
assert.True(t, b)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.True(t, clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||
})
|
||||
|
||||
t.Run("addhost_fail", func(t *testing.T) {
|
||||
b, err := clients.AddHost("1.1.1.1", "host1", ClientSourceRDNS)
|
||||
assert.False(t, b)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestClientsWhois(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package home
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -95,8 +94,8 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, _ *http
|
||||
}
|
||||
|
||||
// Convert JSON object to Client object
|
||||
func jsonToClient(cj clientJSON) (*Client, error) {
|
||||
c := Client{
|
||||
func jsonToClient(cj clientJSON) (c *Client) {
|
||||
return &Client{
|
||||
Name: cj.Name,
|
||||
IDs: cj.IDs,
|
||||
Tags: cj.Tags,
|
||||
@@ -111,7 +110,6 @@ func jsonToClient(cj clientJSON) (*Client, error) {
|
||||
|
||||
Upstreams: cj.Upstreams,
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// Convert Client object to JSON
|
||||
@@ -150,24 +148,15 @@ func clientHostToJSON(ip string, ch ClientHost) clientJSON {
|
||||
|
||||
// Add a new client
|
||||
func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
cj := clientJSON{}
|
||||
err = json.Unmarshal(body, &cj)
|
||||
err := json.NewDecoder(r.Body).Decode(&cj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c, err := jsonToClient(cj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
c := jsonToClient(cj)
|
||||
ok, err := clients.Add(*c)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
@@ -183,16 +172,17 @@ func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.
|
||||
|
||||
// Remove client
|
||||
func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
cj := clientJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&cj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cj := clientJSON{}
|
||||
err = json.Unmarshal(body, &cj)
|
||||
if err != nil || len(cj.Name) == 0 {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
if len(cj.Name) == 0 {
|
||||
httpError(w, http.StatusBadRequest, "client's name must be non-empty")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -211,29 +201,20 @@ type updateJSON struct {
|
||||
|
||||
// Update client's properties
|
||||
func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
dj := updateJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&dj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
httpError(w, http.StatusBadRequest, "failed to process request body: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var dj updateJSON
|
||||
err = json.Unmarshal(body, &dj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
if len(dj.Name) == 0 {
|
||||
httpError(w, http.StatusBadRequest, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
c, err := jsonToClient(dj.Data)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c := jsonToClient(dj.Data)
|
||||
err = clients.Update(dj.Name, *c)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
@@ -99,6 +99,16 @@ type tlsConfigSettings struct {
|
||||
PortDNSOverTLS int `yaml:"port_dns_over_tls" json:"port_dns_over_tls,omitempty"` // DNS-over-TLS port. If 0, DOT will be disabled
|
||||
PortDNSOverQUIC uint16 `yaml:"port_dns_over_quic" json:"port_dns_over_quic,omitempty"` // DNS-over-QUIC port. If 0, DoQ will be disabled
|
||||
|
||||
// PortDNSCrypt is the port for DNSCrypt requests. If it's zero,
|
||||
// DNSCrypt is disabled.
|
||||
PortDNSCrypt int `yaml:"port_dnscrypt" json:"port_dnscrypt"`
|
||||
// DNSCryptConfigFile is the path to the DNSCrypt config file. Must be
|
||||
// set if PortDNSCrypt is not zero.
|
||||
//
|
||||
// See https://github.com/AdguardTeam/dnsproxy and
|
||||
// https://github.com/ameshkov/dnscrypt.
|
||||
DNSCryptConfigFile string `yaml:"dnscrypt_config_file" json:"dnscrypt_config_file"`
|
||||
|
||||
// Allow DOH queries via unencrypted HTTP (e.g. for reverse proxying)
|
||||
AllowUnencryptedDOH bool `yaml:"allow_unencrypted_doh" json:"allow_unencrypted_doh"`
|
||||
|
||||
|
||||
@@ -107,24 +107,24 @@ func registerControlHandlers() {
|
||||
httpRegister(http.MethodGet, "/control/status", handleStatus)
|
||||
httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage)
|
||||
httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage)
|
||||
http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
|
||||
Context.mux.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON)))
|
||||
httpRegister(http.MethodPost, "/control/update", handleUpdate)
|
||||
httpRegister(http.MethodGet, "/control/profile", handleGetProfile)
|
||||
|
||||
// No auth is necessary for DOH/DOT configurations
|
||||
http.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoh))
|
||||
http.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDot))
|
||||
Context.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDOH))
|
||||
Context.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDOT))
|
||||
RegisterAuthHandlers()
|
||||
}
|
||||
|
||||
func httpRegister(method string, url string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
func httpRegister(method, url string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
if len(method) == 0 {
|
||||
// "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method
|
||||
http.HandleFunc(url, postInstall(handler))
|
||||
Context.mux.HandleFunc(url, postInstall(handler))
|
||||
return
|
||||
}
|
||||
|
||||
http.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
||||
Context.mux.Handle(url, postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(ensureHandler(method, handler)))))
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
@@ -201,7 +201,6 @@ func preInstallHandler(handler http.Handler) http.Handler {
|
||||
// it also enforces HTTPS if it is enabled and configured
|
||||
func postInstall(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if Context.firstRun &&
|
||||
!strings.HasPrefix(r.URL.Path, "/install.") &&
|
||||
!strings.HasPrefix(r.URL.Path, "/assets/") {
|
||||
|
||||
@@ -196,9 +196,9 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
if (status&statusUpdateRequired) != 0 && fj.Data.Enabled {
|
||||
// download new filter and apply its rules
|
||||
flags := FilterRefreshBlocklists
|
||||
flags := filterRefreshBlocklists
|
||||
if fj.Whitelist {
|
||||
flags = FilterRefreshAllowlists
|
||||
flags = filterRefreshAllowlists
|
||||
}
|
||||
nUpdated, _ := f.refreshFilters(flags, true)
|
||||
// if at least 1 filter has been updated, refreshFilters() restarts the filtering automatically
|
||||
@@ -214,6 +214,7 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
|
||||
}
|
||||
|
||||
func (f *Filtering) handleFilteringSetRules(w http.ResponseWriter, r *http.Request) {
|
||||
// This use of ReadAll is safe, because request's body is now limited.
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
||||
@@ -243,11 +244,11 @@ func (f *Filtering) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
Context.controlLock.Unlock()
|
||||
flags := FilterRefreshBlocklists
|
||||
flags := filterRefreshBlocklists
|
||||
if req.White {
|
||||
flags = FilterRefreshAllowlists
|
||||
flags = filterRefreshAllowlists
|
||||
}
|
||||
resp.Updated, err = f.refreshFilters(flags|FilterRefreshForce, false)
|
||||
resp.Updated, err = f.refreshFilters(flags|filterRefreshForce, false)
|
||||
Context.controlLock.Lock()
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "%s", err)
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
@@ -167,7 +167,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON {
|
||||
|
||||
if set {
|
||||
// Try to set static IP for the specified interface
|
||||
err := dhcpd.SetStaticIP(interfaceName)
|
||||
err := sysutil.IfaceSetStaticIP(interfaceName)
|
||||
if err != nil {
|
||||
resp.Static = "error"
|
||||
resp.Error = err.Error()
|
||||
@@ -177,7 +177,7 @@ func handleStaticIP(ip string, set bool) staticIPJSON {
|
||||
|
||||
// Fallthrough here even if we set static IP
|
||||
// Check if we have a static IP and return the details
|
||||
isStaticIP, err := dhcpd.HasStaticIP(interfaceName)
|
||||
isStaticIP, err := sysutil.IfaceHasStaticIP(interfaceName)
|
||||
if err != nil {
|
||||
resp.Static = "error"
|
||||
resp.Error = err.Error()
|
||||
@@ -273,7 +273,7 @@ type applyConfigReq struct {
|
||||
}
|
||||
|
||||
// Copy installation parameters between two configuration objects
|
||||
func copyInstallSettings(dst *configuration, src *configuration) {
|
||||
func copyInstallSettings(dst, src *configuration) {
|
||||
dst.BindHost = src.BindHost
|
||||
dst.BindPort = src.BindPort
|
||||
dst.DNS.BindHost = src.DNS.BindHost
|
||||
@@ -372,7 +372,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (web *Web) registerInstallHandlers() {
|
||||
http.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
||||
http.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
||||
http.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
|
||||
Context.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses)))
|
||||
Context.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig)))
|
||||
Context.mux.HandleFunc("/control/install/configure", preInstall(ensurePOST(web.handleInstallConfigure)))
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/update"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
@@ -104,12 +104,7 @@ func getVersionResp(info update.VersionInfo) []byte {
|
||||
tlsConf.PortDNSOverQUIC < 1024)) ||
|
||||
config.BindPort < 1024 ||
|
||||
config.DNS.Port < 1024) {
|
||||
// On UNIX, if we're running under a regular user,
|
||||
// but with CAP_NET_BIND_SERVICE set on a binary file,
|
||||
// and we're listening on ports <1024,
|
||||
// we won't be able to restart after we replace the binary file,
|
||||
// because we'll lose CAP_NET_BIND_SERVICE capability.
|
||||
canUpdate, _ = util.HaveAdminRights()
|
||||
canUpdate, _ = sysutil.CanBindPrivilegedPorts()
|
||||
}
|
||||
ret["can_autoupdate"] = canUpdate
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func TestTargzFileUnpack(t *testing.T) {
|
||||
fn := "../dist/AdGuardHome_linux_amd64.tar.gz"
|
||||
outdir := "../test-unpack"
|
||||
defer os.RemoveAll(outdir)
|
||||
_ = os.Mkdir(outdir, 0755)
|
||||
_ = os.Mkdir(outdir, 0o755)
|
||||
files, e := targzFileUnpack(fn, outdir)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
@@ -92,7 +92,7 @@ func TestTargzFileUnpack(t *testing.T) {
|
||||
func TestZipFileUnpack(t *testing.T) {
|
||||
fn := "../dist/AdGuardHome_windows_amd64.zip"
|
||||
outdir := "../test-unpack"
|
||||
_ = os.Mkdir(outdir, 0755)
|
||||
_ = os.Mkdir(outdir, 0o755)
|
||||
files, e := zipFileUnpack(fn, outdir)
|
||||
if e != nil {
|
||||
t.Fatalf("FAILED: %s", e)
|
||||
@@ -3,8 +3,10 @@ package home
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/agherr"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsfilter"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
@@ -12,6 +14,8 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/ameshkov/dnscrypt/v2"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Called by other modules when configuration is changed
|
||||
@@ -70,7 +74,12 @@ func initDNSServer() error {
|
||||
}
|
||||
Context.dnsServer = dnsforward.NewServer(p)
|
||||
Context.clients.dnsServer = Context.dnsServer
|
||||
dnsConfig := generateServerConfig()
|
||||
dnsConfig, err := generateServerConfig()
|
||||
if err != nil {
|
||||
closeDNSServer()
|
||||
return fmt.Errorf("generateServerConfig: %w", err)
|
||||
}
|
||||
|
||||
err = Context.dnsServer.Prepare(&dnsConfig)
|
||||
if err != nil {
|
||||
closeDNSServer()
|
||||
@@ -88,60 +97,6 @@ func isRunning() bool {
|
||||
return Context.dnsServer != nil && Context.dnsServer.IsRunning()
|
||||
}
|
||||
|
||||
// nolint (gocyclo)
|
||||
// Return TRUE if IP is within public Internet IP range
|
||||
func isPublicIP(ip net.IP) bool {
|
||||
ip4 := ip.To4()
|
||||
if ip4 != nil {
|
||||
switch ip4[0] {
|
||||
case 0:
|
||||
return false // software
|
||||
case 10:
|
||||
return false // private network
|
||||
case 127:
|
||||
return false // loopback
|
||||
case 169:
|
||||
if ip4[1] == 254 {
|
||||
return false // link-local
|
||||
}
|
||||
case 172:
|
||||
if ip4[1] >= 16 && ip4[1] <= 31 {
|
||||
return false // private network
|
||||
}
|
||||
case 192:
|
||||
if (ip4[1] == 0 && ip4[2] == 0) || // private network
|
||||
(ip4[1] == 0 && ip4[2] == 2) || // documentation
|
||||
(ip4[1] == 88 && ip4[2] == 99) || // reserved
|
||||
(ip4[1] == 168) { // private network
|
||||
return false
|
||||
}
|
||||
case 198:
|
||||
if (ip4[1] == 18 || ip4[2] == 19) || // private network
|
||||
(ip4[1] == 51 || ip4[2] == 100) { // documentation
|
||||
return false
|
||||
}
|
||||
case 203:
|
||||
if ip4[1] == 0 && ip4[2] == 113 { // documentation
|
||||
return false
|
||||
}
|
||||
case 224:
|
||||
if ip4[1] == 0 && ip4[2] == 0 { // multicast
|
||||
return false
|
||||
}
|
||||
case 255:
|
||||
if ip4[1] == 255 && ip4[2] == 255 && ip4[3] == 255 { // subnet
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func onDNSRequest(d *proxy.DNSContext) {
|
||||
ip := dnsforward.GetIPString(d.Addr)
|
||||
if ip == "" {
|
||||
@@ -153,15 +108,16 @@ func onDNSRequest(d *proxy.DNSContext) {
|
||||
if !ipAddr.IsLoopback() {
|
||||
Context.rdns.Begin(ip)
|
||||
}
|
||||
if isPublicIP(ipAddr) {
|
||||
if !Context.ipDetector.detectSpecialNetwork(ipAddr) {
|
||||
Context.whois.Begin(ip)
|
||||
}
|
||||
}
|
||||
|
||||
func generateServerConfig() dnsforward.ServerConfig {
|
||||
newconfig := dnsforward.ServerConfig{
|
||||
UDPListenAddr: &net.UDPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port},
|
||||
TCPListenAddr: &net.TCPAddr{IP: net.ParseIP(config.DNS.BindHost), Port: config.DNS.Port},
|
||||
func generateServerConfig() (newconfig dnsforward.ServerConfig, err error) {
|
||||
bindHost := net.ParseIP(config.DNS.BindHost)
|
||||
newconfig = dnsforward.ServerConfig{
|
||||
UDPListenAddr: &net.UDPAddr{IP: bindHost, Port: config.DNS.Port},
|
||||
TCPListenAddr: &net.TCPAddr{IP: bindHost, Port: config.DNS.Port},
|
||||
FilteringConfig: config.DNS.FilteringConfig,
|
||||
ConfigModified: onConfigModified,
|
||||
HTTPRegister: httpRegister,
|
||||
@@ -175,35 +131,86 @@ func generateServerConfig() dnsforward.ServerConfig {
|
||||
|
||||
if tlsConf.PortDNSOverTLS != 0 {
|
||||
newconfig.TLSListenAddr = &net.TCPAddr{
|
||||
IP: net.ParseIP(config.DNS.BindHost),
|
||||
IP: bindHost,
|
||||
Port: tlsConf.PortDNSOverTLS,
|
||||
}
|
||||
}
|
||||
|
||||
if tlsConf.PortDNSOverQUIC != 0 {
|
||||
newconfig.QUICListenAddr = &net.UDPAddr{
|
||||
IP: net.ParseIP(config.DNS.BindHost),
|
||||
IP: bindHost,
|
||||
Port: int(tlsConf.PortDNSOverQUIC),
|
||||
}
|
||||
}
|
||||
|
||||
if tlsConf.PortDNSCrypt != 0 {
|
||||
newconfig.DNSCryptConfig, err = newDNSCrypt(bindHost, tlsConf)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's already
|
||||
// wrapped by newDNSCrypt.
|
||||
return dnsforward.ServerConfig{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newconfig.TLSv12Roots = Context.tlsRoots
|
||||
newconfig.TLSCiphers = Context.tlsCiphers
|
||||
newconfig.TLSAllowUnencryptedDOH = tlsConf.AllowUnencryptedDOH
|
||||
|
||||
newconfig.FilterHandler = applyAdditionalFiltering
|
||||
newconfig.GetCustomUpstreamByClient = Context.clients.FindUpstreams
|
||||
return newconfig
|
||||
|
||||
return newconfig, nil
|
||||
}
|
||||
|
||||
type DNSEncryption struct {
|
||||
func newDNSCrypt(bindHost net.IP, tlsConf tlsConfigSettings) (dnscc dnsforward.DNSCryptConfig, err error) {
|
||||
if tlsConf.DNSCryptConfigFile == "" {
|
||||
return dnscc, agherr.Error("no dnscrypt_config_file")
|
||||
}
|
||||
|
||||
f, err := os.Open(tlsConf.DNSCryptConfigFile)
|
||||
if err != nil {
|
||||
return dnscc, fmt.Errorf("opening dnscrypt config: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
rc := &dnscrypt.ResolverConfig{}
|
||||
err = yaml.NewDecoder(f).Decode(rc)
|
||||
if err != nil {
|
||||
return dnscc, fmt.Errorf("decoding dnscrypt config: %w", err)
|
||||
}
|
||||
|
||||
cert, err := rc.CreateCert()
|
||||
if err != nil {
|
||||
return dnscc, fmt.Errorf("creating dnscrypt cert: %w", err)
|
||||
}
|
||||
|
||||
udpAddr := &net.UDPAddr{
|
||||
IP: bindHost,
|
||||
Port: tlsConf.PortDNSCrypt,
|
||||
}
|
||||
tcpAddr := &net.TCPAddr{
|
||||
IP: bindHost,
|
||||
Port: tlsConf.PortDNSCrypt,
|
||||
}
|
||||
|
||||
return dnsforward.DNSCryptConfig{
|
||||
UDPListenAddr: udpAddr,
|
||||
TCPListenAddr: tcpAddr,
|
||||
ResolverCert: cert,
|
||||
ProviderName: rc.ProviderName,
|
||||
Enabled: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type dnsEncryption struct {
|
||||
https string
|
||||
tls string
|
||||
quic string
|
||||
}
|
||||
|
||||
func getDNSEncryption() DNSEncryption {
|
||||
dnsEncryption := DNSEncryption{}
|
||||
func getDNSEncryption() dnsEncryption {
|
||||
dnsEncryption := dnsEncryption{}
|
||||
|
||||
tlsConf := tlsConfigSettings{}
|
||||
|
||||
@@ -327,7 +334,7 @@ func startDNSServer() error {
|
||||
if !ipAddr.IsLoopback() {
|
||||
Context.rdns.Begin(ip)
|
||||
}
|
||||
if isPublicIP(ipAddr) {
|
||||
if !Context.ipDetector.detectSpecialNetwork(ipAddr) {
|
||||
Context.whois.Begin(ip)
|
||||
}
|
||||
}
|
||||
@@ -335,11 +342,16 @@ func startDNSServer() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func reconfigureDNSServer() error {
|
||||
newconfig := generateServerConfig()
|
||||
err := Context.dnsServer.Reconfigure(&newconfig)
|
||||
func reconfigureDNSServer() (err error) {
|
||||
var newconfig dnsforward.ServerConfig
|
||||
newconfig, err = generateServerConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't start forwarding DNS server: %w", err)
|
||||
return fmt.Errorf("generating forwarding dns server config: %w", err)
|
||||
}
|
||||
|
||||
err = Context.dnsServer.Reconfigure(&newconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting forwarding dns server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -254,7 +255,7 @@ func (f *Filtering) periodicallyRefreshFilters() {
|
||||
isNetworkErr := false
|
||||
if config.DNS.FiltersUpdateIntervalHours != 0 && atomic.CompareAndSwapUint32(&f.refreshStatus, 0, 1) {
|
||||
f.refreshLock.Lock()
|
||||
_, isNetworkErr = f.refreshFiltersIfNecessary(FilterRefreshBlocklists | FilterRefreshAllowlists)
|
||||
_, isNetworkErr = f.refreshFiltersIfNecessary(filterRefreshBlocklists | filterRefreshAllowlists)
|
||||
f.refreshLock.Unlock()
|
||||
f.refreshStatus = 0
|
||||
if !isNetworkErr {
|
||||
@@ -274,7 +275,7 @@ func (f *Filtering) periodicallyRefreshFilters() {
|
||||
}
|
||||
|
||||
// Refresh filters
|
||||
// flags: FilterRefresh*
|
||||
// flags: filterRefresh*
|
||||
// important:
|
||||
// TRUE: ignore the fact that we're currently updating the filters
|
||||
func (f *Filtering) refreshFilters(flags int, important bool) (int, error) {
|
||||
@@ -367,14 +368,14 @@ func (f *Filtering) refreshFiltersArray(filters *[]filter, force bool) (int, []f
|
||||
}
|
||||
|
||||
const (
|
||||
FilterRefreshForce = 1 // ignore last file modification date
|
||||
FilterRefreshAllowlists = 2 // update allow-lists
|
||||
FilterRefreshBlocklists = 4 // update block-lists
|
||||
filterRefreshForce = 1 // ignore last file modification date
|
||||
filterRefreshAllowlists = 2 // update allow-lists
|
||||
filterRefreshBlocklists = 4 // update block-lists
|
||||
)
|
||||
|
||||
// Checks filters updates if necessary
|
||||
// If force is true, it ignores the filter.LastUpdated field value
|
||||
// flags: FilterRefresh*
|
||||
// flags: filterRefresh*
|
||||
//
|
||||
// Algorithm:
|
||||
// . Get the list of filters to be updated
|
||||
@@ -400,13 +401,13 @@ func (f *Filtering) refreshFiltersIfNecessary(flags int) (int, bool) {
|
||||
netError := false
|
||||
netErrorW := false
|
||||
force := false
|
||||
if (flags & FilterRefreshForce) != 0 {
|
||||
if (flags & filterRefreshForce) != 0 {
|
||||
force = true
|
||||
}
|
||||
if (flags & FilterRefreshBlocklists) != 0 {
|
||||
if (flags & filterRefreshBlocklists) != 0 {
|
||||
updateCount, updateFilters, updateFlags, netError = f.refreshFiltersArray(&config.Filters, force)
|
||||
}
|
||||
if (flags & FilterRefreshAllowlists) != 0 {
|
||||
if (flags & filterRefreshAllowlists) != 0 {
|
||||
updateCountW := 0
|
||||
var updateFiltersW []filter
|
||||
var updateFlagsW []bool
|
||||
@@ -497,46 +498,7 @@ func (f *Filtering) update(filter *filter) (bool, error) {
|
||||
return b, err
|
||||
}
|
||||
|
||||
// nolint(gocyclo)
|
||||
func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||
log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL)
|
||||
|
||||
tmpFile, err := ioutil.TempFile(filepath.Join(Context.getDataDir(), filterDir), "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
if tmpFile != nil {
|
||||
_ = tmpFile.Close()
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
var reader io.Reader
|
||||
if filepath.IsAbs(filter.URL) {
|
||||
f, err := os.Open(filter.URL)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("open file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
reader = f
|
||||
} else {
|
||||
resp, err := Context.client.Get(filter.URL)
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL)
|
||||
return false, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
|
||||
}
|
||||
reader = resp.Body
|
||||
}
|
||||
|
||||
func (f *Filtering) read(reader io.Reader, tmpFile *os.File, filter *filter) (int, error) {
|
||||
htmlTest := true
|
||||
firstChunk := make([]byte, 4*1024)
|
||||
firstChunkLen := 0
|
||||
@@ -556,12 +518,12 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||
|
||||
if firstChunkLen == len(firstChunk) || err == io.EOF {
|
||||
if !isPrintableText(firstChunk, firstChunkLen) {
|
||||
return false, fmt.Errorf("data contains non-printable characters")
|
||||
return total, fmt.Errorf("data contains non-printable characters")
|
||||
}
|
||||
|
||||
s := strings.ToLower(string(firstChunk))
|
||||
if strings.Contains(s, "<html") || strings.Contains(s, "<!doctype") {
|
||||
return false, fmt.Errorf("data is HTML, not plain text")
|
||||
return total, fmt.Errorf("data is HTML, not plain text")
|
||||
}
|
||||
|
||||
htmlTest = false
|
||||
@@ -571,17 +533,70 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||
|
||||
_, err2 := tmpFile.Write(buf[:n])
|
||||
if err2 != nil {
|
||||
return false, err2
|
||||
return total, err2
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
return total, nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Couldn't fetch filter contents from URL %s, skipping: %s", filter.URL, err)
|
||||
return false, err
|
||||
return total, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updateIntl returns true if filter update performed successfully.
|
||||
func (f *Filtering) updateIntl(filter *filter) (updated bool, err error) {
|
||||
updated = false
|
||||
log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL)
|
||||
|
||||
tmpFile, err := ioutil.TempFile(filepath.Join(Context.getDataDir(), filterDir), "")
|
||||
if err != nil {
|
||||
return updated, err
|
||||
}
|
||||
defer func() {
|
||||
if tmpFile != nil {
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
log.Printf("Couldn't close temporary file: %s", err)
|
||||
}
|
||||
tmpFileName := tmpFile.Name()
|
||||
if err := os.Remove(tmpFileName); err != nil {
|
||||
log.Printf("Couldn't delete temporary file %s: %s", tmpFileName, err)
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
var reader io.Reader
|
||||
if filepath.IsAbs(filter.URL) {
|
||||
f, err := os.Open(filter.URL)
|
||||
if err != nil {
|
||||
return updated, fmt.Errorf("open file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
reader = f
|
||||
} else {
|
||||
resp, err := Context.client.Get(filter.URL)
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err)
|
||||
return updated, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL)
|
||||
return updated, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
|
||||
}
|
||||
reader = resp.Body
|
||||
}
|
||||
|
||||
total, err := f.read(reader, tmpFile, filter)
|
||||
if err != nil {
|
||||
return updated, err
|
||||
}
|
||||
|
||||
// Extract filter name and count number of rules
|
||||
_, _ = tmpFile.Seek(0, io.SeekStart)
|
||||
@@ -589,7 +604,7 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||
// Check if the filter has been really changed
|
||||
if filter.checksum == checksum {
|
||||
log.Tracef("Filter #%d at URL %s hasn't changed, not updating it", filter.ID, filter.URL)
|
||||
return false, nil
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
log.Printf("Filter %d has been updated: %d bytes, %d rules",
|
||||
@@ -606,11 +621,12 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
|
||||
_ = tmpFile.Close()
|
||||
err = os.Rename(tmpFile.Name(), filterFilePath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return updated, err
|
||||
}
|
||||
tmpFile = nil
|
||||
updated = true
|
||||
|
||||
return true, nil
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// loads filter contents from the file in dataDir
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func testStartFilterListener() net.Listener {
|
||||
http.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/filters/1.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||
content := `||example.org^$third-party
|
||||
# Inline comment example
|
||||
||example.com^$third-party
|
||||
@@ -26,7 +27,7 @@ func testStartFilterListener() net.Listener {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() { _ = http.Serve(listener, nil) }()
|
||||
go func() { _ = http.Serve(listener, mux) }()
|
||||
return listener
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/querylog"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/stats"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/update"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -43,6 +44,7 @@ var (
|
||||
updateChannel = "none"
|
||||
versionCheckURL = ""
|
||||
ARMVersion = ""
|
||||
MIPSVersion = ""
|
||||
)
|
||||
|
||||
// Global context
|
||||
@@ -65,6 +67,11 @@ type homeContext struct {
|
||||
autoHosts util.AutoHosts // IP-hostname pairs taken from system configuration (e.g. /etc/hosts) files
|
||||
updater *update.Updater
|
||||
|
||||
ipDetector *ipDetector
|
||||
|
||||
// mux is our custom http.ServeMux.
|
||||
mux *http.ServeMux
|
||||
|
||||
// Runtime properties
|
||||
// --
|
||||
|
||||
@@ -92,11 +99,12 @@ func (c *homeContext) getDataDir() string {
|
||||
var Context homeContext
|
||||
|
||||
// Main is the entry point
|
||||
func Main(version, channel, armVer string) {
|
||||
func Main(version, channel, armVer, mipsVer string) {
|
||||
// Init update-related global variables
|
||||
versionString = version
|
||||
updateChannel = channel
|
||||
ARMVersion = armVer
|
||||
MIPSVersion = mipsVer
|
||||
versionCheckURL = "https://static.adguard.com/adguardhome/" + updateChannel + "/version.json"
|
||||
|
||||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||
@@ -133,35 +141,19 @@ func Main(version, channel, armVer string) {
|
||||
|
||||
// version - returns the current version string
|
||||
func version() string {
|
||||
// TODO(a.garipov): I'm pretty sure we can extract some of this stuff
|
||||
// from the build info.
|
||||
msg := "AdGuard Home, version %s, channel %s, arch %s %s"
|
||||
if ARMVersion != "" {
|
||||
msg = msg + " v" + ARMVersion
|
||||
} else if MIPSVersion != "" {
|
||||
msg = msg + " " + MIPSVersion
|
||||
}
|
||||
|
||||
return fmt.Sprintf(msg, versionString, updateChannel, runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// run initializes configuration and runs the AdGuard Home
|
||||
// run is a blocking method!
|
||||
// nolint
|
||||
func run(args options) {
|
||||
// configure config filename
|
||||
initConfigFilename(args)
|
||||
|
||||
// configure working dir and config path
|
||||
initWorkingDir(args)
|
||||
|
||||
// configure log level and output
|
||||
configureLogger(args)
|
||||
|
||||
// Go memory hacks
|
||||
memoryUsage(args)
|
||||
|
||||
// print the first message after logger is configured
|
||||
log.Println(version())
|
||||
log.Debug("Current working directory is %s", Context.workDir)
|
||||
if args.runningAsService {
|
||||
log.Info("AdGuard Home is running as a service")
|
||||
}
|
||||
func setupContext(args options) {
|
||||
Context.runningAsService = args.runningAsService
|
||||
Context.disableUpdate = args.disableUpdate
|
||||
|
||||
@@ -179,7 +171,8 @@ func run(args options) {
|
||||
DialContext: customDialContext,
|
||||
Proxy: getHTTPProxy,
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: Context.tlsRoots,
|
||||
RootCAs: Context.tlsRoots,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
}
|
||||
Context.client = &http.Client{
|
||||
@@ -206,11 +199,10 @@ func run(args options) {
|
||||
}
|
||||
}
|
||||
|
||||
// 'clients' module uses 'dnsfilter' module's static data (dnsfilter.BlockedSvcKnown()),
|
||||
// so we have to initialize dnsfilter's static data first,
|
||||
// but also avoid relying on automatic Go init() function
|
||||
dnsfilter.InitModule()
|
||||
Context.mux = http.NewServeMux()
|
||||
}
|
||||
|
||||
func setupConfig(args options) {
|
||||
config.DHCP.WorkDir = Context.workDir
|
||||
config.DHCP.HTTPRegister = httpRegister
|
||||
config.DHCP.ConfigModified = onConfigModified
|
||||
@@ -238,7 +230,7 @@ func run(args options) {
|
||||
|
||||
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
|
||||
config.RlimitNoFile != 0 {
|
||||
util.SetRlimit(config.RlimitNoFile)
|
||||
sysutil.SetRlimit(config.RlimitNoFile)
|
||||
}
|
||||
|
||||
// override bind host/port from the console
|
||||
@@ -251,6 +243,37 @@ func run(args options) {
|
||||
if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
|
||||
Context.pidFileName = args.pidFile
|
||||
}
|
||||
}
|
||||
|
||||
// run performs configurating and starts AdGuard Home.
|
||||
func run(args options) {
|
||||
// configure config filename
|
||||
initConfigFilename(args)
|
||||
|
||||
// configure working dir and config path
|
||||
initWorkingDir(args)
|
||||
|
||||
// configure log level and output
|
||||
configureLogger(args)
|
||||
|
||||
// Go memory hacks
|
||||
memoryUsage(args)
|
||||
|
||||
// print the first message after logger is configured
|
||||
log.Println(version())
|
||||
log.Debug("Current working directory is %s", Context.workDir)
|
||||
if args.runningAsService {
|
||||
log.Info("AdGuard Home is running as a service")
|
||||
}
|
||||
|
||||
setupContext(args)
|
||||
|
||||
// clients package uses dnsfilter package's static data (dnsfilter.BlockedSvcKnown()),
|
||||
// so we have to initialize dnsfilter's static data first,
|
||||
// but also avoid relying on automatic Go init() function
|
||||
dnsfilter.InitModule()
|
||||
|
||||
setupConfig(args)
|
||||
|
||||
if !Context.firstRun {
|
||||
// Save the updated config
|
||||
@@ -292,10 +315,14 @@ func run(args options) {
|
||||
log.Fatalf("Can't initialize TLS module")
|
||||
}
|
||||
|
||||
webConf := WebConfig{
|
||||
webConf := webConfig{
|
||||
firstRun: Context.firstRun,
|
||||
BindHost: config.BindHost,
|
||||
BindPort: config.BindPort,
|
||||
|
||||
ReadTimeout: ReadTimeout,
|
||||
ReadHeaderTimeout: ReadHeaderTimeout,
|
||||
WriteTimeout: WriteTimeout,
|
||||
}
|
||||
Context.web = CreateWeb(&webConf)
|
||||
if Context.web == nil {
|
||||
@@ -322,6 +349,11 @@ func run(args options) {
|
||||
}
|
||||
}
|
||||
|
||||
Context.ipDetector, err = newIPDetector()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
Context.web.Start()
|
||||
|
||||
// wait indefinitely for other go-routines to complete their job
|
||||
@@ -352,7 +384,7 @@ func checkPermissions() {
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows we need to have admin rights to run properly
|
||||
|
||||
admin, _ := util.HaveAdminRights()
|
||||
admin, _ := sysutil.HaveAdminRights()
|
||||
if admin {
|
||||
return
|
||||
}
|
||||
@@ -469,7 +501,7 @@ func configureLogger(args options) {
|
||||
|
||||
if ls.LogFile == configSyslog {
|
||||
// Use syslog where it is possible and eventlog on Windows
|
||||
err := util.ConfigureSyslog(serviceName)
|
||||
err := sysutil.ConfigureSyslog(serviceName)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot initialize syslog: %s", err)
|
||||
}
|
||||
@@ -659,3 +691,12 @@ func getHTTPProxy(req *http.Request) (*url.URL, error) {
|
||||
}
|
||||
return url.Parse(config.ProxyURL)
|
||||
}
|
||||
|
||||
// jsonError is a generic JSON error response.
|
||||
//
|
||||
// TODO(a.garipov): Merge together with the implementations in .../dhcpd and
|
||||
// other packages after refactoring the web handler registering.
|
||||
type jsonError struct {
|
||||
// Message is the error message, an opaque string.
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func TestHome(t *testing.T) {
|
||||
fn := filepath.Join(dir, "AdGuardHome.yaml")
|
||||
|
||||
// Prepare the test config
|
||||
assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0644) == nil)
|
||||
assert.True(t, ioutil.WriteFile(fn, []byte(yamlConf), 0o644) == nil)
|
||||
fn, _ = filepath.Abs(fn)
|
||||
|
||||
config = configuration{} // the global variable is dirty because of the previous tests run
|
||||
|
||||
@@ -66,6 +66,7 @@ func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func handleI18nChangeLanguage(w http.ResponseWriter, r *http.Request) {
|
||||
// This use of ReadAll is safe, because request's body is now limited.
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failed to read request body: %s", err)
|
||||
|
||||
72
internal/home/ipdetector.go
Normal file
72
internal/home/ipdetector.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package home
|
||||
|
||||
import "net"
|
||||
|
||||
// ipDetector describes IP address properties.
|
||||
type ipDetector struct {
|
||||
nets []*net.IPNet
|
||||
}
|
||||
|
||||
// newIPDetector returns a new IP detector.
|
||||
func newIPDetector() (ipd *ipDetector, err error) {
|
||||
specialNetworks := []string{
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"100.64.0.0/10",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24",
|
||||
"192.0.0.0/29",
|
||||
"192.0.2.0/24",
|
||||
"192.88.99.0/24",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
"198.51.100.0/24",
|
||||
"203.0.113.0/24",
|
||||
"240.0.0.0/4",
|
||||
"255.255.255.255/32",
|
||||
"::1/128",
|
||||
"::/128",
|
||||
"64:ff9b::/96",
|
||||
// Since this network is used for mapping IPv4 addresses, we
|
||||
// don't include it.
|
||||
// "::ffff:0:0/96",
|
||||
"100::/64",
|
||||
"2001::/23",
|
||||
"2001::/32",
|
||||
"2001:2::/48",
|
||||
"2001:db8::/32",
|
||||
"2001:10::/28",
|
||||
"2002::/16",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
}
|
||||
|
||||
ipd = &ipDetector{
|
||||
nets: make([]*net.IPNet, len(specialNetworks)),
|
||||
}
|
||||
for i, ipnetStr := range specialNetworks {
|
||||
_, ipnet, err := net.ParseCIDR(ipnetStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ipd.nets[i] = ipnet
|
||||
}
|
||||
|
||||
return ipd, nil
|
||||
}
|
||||
|
||||
// detectSpecialNetwork returns true if IP address is contained by any of
|
||||
// special-purpose IP address registries according to RFC-6890
|
||||
// (https://tools.ietf.org/html/rfc6890).
|
||||
func (ipd *ipDetector) detectSpecialNetwork(ip net.IP) bool {
|
||||
for _, ipnet := range ipd.nets {
|
||||
if ipnet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
146
internal/home/ipdetector_test.go
Normal file
146
internal/home/ipdetector_test.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIPDetector_detectSpecialNetwork(t *testing.T) {
|
||||
var ipd *ipDetector
|
||||
|
||||
t.Run("newIPDetector", func(t *testing.T) {
|
||||
var err error
|
||||
ipd, err = newIPDetector()
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
ip net.IP
|
||||
want bool
|
||||
}{{
|
||||
name: "not_specific",
|
||||
ip: net.ParseIP("8.8.8.8"),
|
||||
want: false,
|
||||
}, {
|
||||
name: "this_host_on_this_network",
|
||||
ip: net.ParseIP("0.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "private-Use",
|
||||
ip: net.ParseIP("10.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "shared_address_space",
|
||||
ip: net.ParseIP("100.64.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "loopback",
|
||||
ip: net.ParseIP("127.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "link_local",
|
||||
ip: net.ParseIP("169.254.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "private-use",
|
||||
ip: net.ParseIP("172.16.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "ietf_protocol_assignments",
|
||||
ip: net.ParseIP("192.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "ds-lite",
|
||||
ip: net.ParseIP("192.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation_(test-net-1)",
|
||||
ip: net.ParseIP("192.0.2.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "6to4_relay_anycast",
|
||||
ip: net.ParseIP("192.88.99.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "private-use",
|
||||
ip: net.ParseIP("192.168.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "benchmarking",
|
||||
ip: net.ParseIP("198.18.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation_(test-net-2)",
|
||||
ip: net.ParseIP("198.51.100.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation_(test-net-3)",
|
||||
ip: net.ParseIP("203.0.113.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "reserved",
|
||||
ip: net.ParseIP("240.0.0.0"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "limited_broadcast",
|
||||
ip: net.ParseIP("255.255.255.255"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "loopback_address",
|
||||
ip: net.ParseIP("::1"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "unspecified_address",
|
||||
ip: net.ParseIP("::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "ipv4-ipv6_translation",
|
||||
ip: net.ParseIP("64:ff9b::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "discard-only_address_block",
|
||||
ip: net.ParseIP("100::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "ietf_protocol_assignments",
|
||||
ip: net.ParseIP("2001::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "teredo",
|
||||
ip: net.ParseIP("2001::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "benchmarking",
|
||||
ip: net.ParseIP("2001:2::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "documentation",
|
||||
ip: net.ParseIP("2001:db8::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "orchid",
|
||||
ip: net.ParseIP("2001:10::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "6to4",
|
||||
ip: net.ParseIP("2002::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "unique-local",
|
||||
ip: net.ParseIP("fc00::"),
|
||||
want: true,
|
||||
}, {
|
||||
name: "linked-scoped_unicast",
|
||||
ip: net.ParseIP("fe80::"),
|
||||
want: true,
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, ipd.detectSpecialNetwork(tc.ip))
|
||||
})
|
||||
}
|
||||
}
|
||||
42
internal/home/middlewares.go
Normal file
42
internal/home/middlewares.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
// middlerware is a wrapper function signature.
|
||||
type middleware func(http.Handler) http.Handler
|
||||
|
||||
// withMiddlewares consequently wraps h with all the middlewares.
|
||||
func withMiddlewares(h http.Handler, middlewares ...middleware) (wrapped http.Handler) {
|
||||
wrapped = h
|
||||
|
||||
for _, mw := range middlewares {
|
||||
wrapped = mw(wrapped)
|
||||
}
|
||||
|
||||
return wrapped
|
||||
}
|
||||
|
||||
// RequestBodySizeLimit is maximum request body length in bytes.
|
||||
const RequestBodySizeLimit = 64 * 1024
|
||||
|
||||
// limitRequestBody wraps underlying handler h, making it's request's body Read
|
||||
// method limited.
|
||||
func limitRequestBody(h http.Handler) (limited http.Handler) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
r.Body, err = aghio.LimitReadCloser(r.Body, RequestBodySizeLimit)
|
||||
if err != nil {
|
||||
log.Error("limitRequestBody: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
64
internal/home/middlewares_test.go
Normal file
64
internal/home/middlewares_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLimitRequestBody(t *testing.T) {
|
||||
errReqLimitReached := &aghio.LimitReachedError{
|
||||
Limit: RequestBodySizeLimit,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
body string
|
||||
want []byte
|
||||
wantErr error
|
||||
}{{
|
||||
name: "not_so_big",
|
||||
body: "somestr",
|
||||
want: []byte("somestr"),
|
||||
wantErr: nil,
|
||||
}, {
|
||||
name: "so_big",
|
||||
body: string(make([]byte, RequestBodySizeLimit+1)),
|
||||
want: make([]byte, RequestBodySizeLimit),
|
||||
wantErr: errReqLimitReached,
|
||||
}, {
|
||||
name: "empty",
|
||||
body: "",
|
||||
want: []byte(nil),
|
||||
wantErr: nil,
|
||||
}}
|
||||
|
||||
makeHandler := func(err *error) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var b []byte
|
||||
b, *err = ioutil.ReadAll(r.Body)
|
||||
w.Write(b)
|
||||
})
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var err error
|
||||
handler := makeHandler(&err)
|
||||
lim := limitRequestBody(handler)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "https://www.example.com", strings.NewReader(tc.body))
|
||||
res := httptest.NewRecorder()
|
||||
|
||||
lim.ServeHTTP(res, req)
|
||||
|
||||
assert.Equal(t, tc.want, res.Body.Bytes())
|
||||
assert.Equal(t, tc.wantErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type DNSSettings struct {
|
||||
type dnsSettings struct {
|
||||
DNSProtocol string
|
||||
ServerURL string `plist:",omitempty"`
|
||||
ServerName string `plist:",omitempty"`
|
||||
}
|
||||
|
||||
type PayloadContent struct {
|
||||
type payloadContent struct {
|
||||
Name string
|
||||
PayloadDescription string
|
||||
PayloadDisplayName string
|
||||
@@ -23,11 +24,11 @@ type PayloadContent struct {
|
||||
PayloadType string
|
||||
PayloadUUID string
|
||||
PayloadVersion int
|
||||
DNSSettings DNSSettings
|
||||
DNSSettings dnsSettings
|
||||
}
|
||||
|
||||
type MobileConfig struct {
|
||||
PayloadContent []PayloadContent
|
||||
type mobileConfig struct {
|
||||
PayloadContent []payloadContent
|
||||
PayloadDescription string
|
||||
PayloadDisplayName string
|
||||
PayloadIdentifier string
|
||||
@@ -46,19 +47,20 @@ const (
|
||||
dnsProtoTLS = "TLS"
|
||||
)
|
||||
|
||||
func getMobileConfig(d DNSSettings) ([]byte, error) {
|
||||
func getMobileConfig(d dnsSettings) ([]byte, error) {
|
||||
var name string
|
||||
switch d.DNSProtocol {
|
||||
case dnsProtoHTTPS:
|
||||
name = fmt.Sprintf("%s DoH", d.ServerName)
|
||||
d.ServerURL = fmt.Sprintf("https://%s/dns-query", d.ServerName)
|
||||
case dnsProtoTLS:
|
||||
name = fmt.Sprintf("%s DoT", d.ServerName)
|
||||
default:
|
||||
return nil, fmt.Errorf("bad dns protocol %q", d.DNSProtocol)
|
||||
}
|
||||
|
||||
data := MobileConfig{
|
||||
PayloadContent: []PayloadContent{{
|
||||
data := mobileConfig{
|
||||
PayloadContent: []payloadContent{{
|
||||
Name: name,
|
||||
PayloadDescription: "Configures device to use AdGuard Home",
|
||||
PayloadDisplayName: name,
|
||||
@@ -80,34 +82,46 @@ func getMobileConfig(d DNSSettings) ([]byte, error) {
|
||||
return plist.MarshalIndent(data, plist.XMLFormat, "\t")
|
||||
}
|
||||
|
||||
func handleMobileConfig(w http.ResponseWriter, d DNSSettings) {
|
||||
func handleMobileConfig(w http.ResponseWriter, r *http.Request, dnsp string) {
|
||||
host := r.URL.Query().Get("host")
|
||||
if host == "" {
|
||||
host = Context.tls.conf.ServerName
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
const msg = "no host in query parameters and no server_name"
|
||||
err := json.NewEncoder(w).Encode(&jsonError{
|
||||
Message: msg,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("writing 500 json response: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
d := dnsSettings{
|
||||
DNSProtocol: dnsp,
|
||||
ServerName: host,
|
||||
}
|
||||
|
||||
mobileconfig, err := getMobileConfig(d)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "plist.MarshalIndent: %s", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
_, _ = w.Write(mobileconfig)
|
||||
}
|
||||
|
||||
func handleMobileConfigDoh(w http.ResponseWriter, r *http.Request) {
|
||||
handleMobileConfig(w, DNSSettings{
|
||||
DNSProtocol: dnsProtoHTTPS,
|
||||
ServerURL: fmt.Sprintf("https://%s/dns-query", r.Host),
|
||||
})
|
||||
func handleMobileConfigDOH(w http.ResponseWriter, r *http.Request) {
|
||||
handleMobileConfig(w, r, dnsProtoHTTPS)
|
||||
}
|
||||
|
||||
func handleMobileConfigDot(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
var host string
|
||||
host, _, err = net.SplitHostPort(r.Host)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "getting host: %s", err)
|
||||
}
|
||||
|
||||
handleMobileConfig(w, DNSSettings{
|
||||
DNSProtocol: dnsProtoTLS,
|
||||
ServerName: host,
|
||||
})
|
||||
func handleMobileConfigDOT(w http.ResponseWriter, r *http.Request) {
|
||||
handleMobileConfig(w, r, dnsProtoTLS)
|
||||
}
|
||||
|
||||
@@ -9,25 +9,132 @@ import (
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
func TestHandleMobileConfigDot(t *testing.T) {
|
||||
var err error
|
||||
func TestHandleMobileConfigDOH(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig?host=example.org", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var r *http.Request
|
||||
r, err = http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
|
||||
assert.Nil(t, err)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
handleMobileConfigDOH(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
handleMobileConfigDot(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var mc mobileConfig
|
||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var mc MobileConfig
|
||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||
assert.Nil(t, err)
|
||||
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
|
||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
||||
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||
assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL)
|
||||
}
|
||||
})
|
||||
|
||||
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||
assert.Equal(t, "example.com DoT", mc.PayloadContent[0].Name)
|
||||
assert.Equal(t, "example.com DoT", mc.PayloadContent[0].PayloadDisplayName)
|
||||
assert.Equal(t, "example.com", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||
}
|
||||
t.Run("success_no_host", func(t *testing.T) {
|
||||
oldTLSConf := Context.tls
|
||||
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||
|
||||
Context.tls = &TLSMod{
|
||||
conf: tlsConfigSettings{ServerName: "example.org"},
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handleMobileConfigDOH(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var mc mobileConfig
|
||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||
assert.Nil(t, err)
|
||||
|
||||
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].Name)
|
||||
assert.Equal(t, "example.org DoH", mc.PayloadContent[0].PayloadDisplayName)
|
||||
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||
assert.Equal(t, "https://example.org/dns-query", mc.PayloadContent[0].DNSSettings.ServerURL)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error_no_host", func(t *testing.T) {
|
||||
oldTLSConf := Context.tls
|
||||
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||
|
||||
Context.tls = &TLSMod{conf: tlsConfigSettings{}}
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handleMobileConfigDOH(w, r)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleMobileConfigDOT(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig?host=example.org", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handleMobileConfigDOT(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var mc mobileConfig
|
||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||
assert.Nil(t, err)
|
||||
|
||||
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
|
||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
||||
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success_no_host", func(t *testing.T) {
|
||||
oldTLSConf := Context.tls
|
||||
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||
|
||||
Context.tls = &TLSMod{
|
||||
conf: tlsConfigSettings{ServerName: "example.org"},
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handleMobileConfigDOT(w, r)
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var mc mobileConfig
|
||||
_, err = plist.Unmarshal(w.Body.Bytes(), &mc)
|
||||
assert.Nil(t, err)
|
||||
|
||||
if assert.Equal(t, 1, len(mc.PayloadContent)) {
|
||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].Name)
|
||||
assert.Equal(t, "example.org DoT", mc.PayloadContent[0].PayloadDisplayName)
|
||||
assert.Equal(t, "example.org", mc.PayloadContent[0].DNSSettings.ServerName)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error_no_host", func(t *testing.T) {
|
||||
oldTLSConf := Context.tls
|
||||
t.Cleanup(func() { Context.tls = oldTLSConf })
|
||||
|
||||
Context.tls = &TLSMod{conf: tlsConfigSettings{}}
|
||||
|
||||
r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handleMobileConfigDOT(w, r)
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/sysutil"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/kardianos/service"
|
||||
@@ -109,7 +110,7 @@ func sendSigReload() {
|
||||
log.Error("Can't read PID file %s: %s", pidfile, err)
|
||||
return
|
||||
}
|
||||
err = util.SendProcessSignal(pid, syscall.SIGHUP)
|
||||
err = sysutil.SendProcessSignal(pid, syscall.SIGHUP)
|
||||
if err != nil {
|
||||
log.Error("Can't send signal to PID %d: %s", pid, err)
|
||||
return
|
||||
|
||||
@@ -3,9 +3,9 @@ package home
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"runtime"
|
||||
|
||||
"github.com/AdguardTeam/golibs/file"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -122,7 +122,7 @@ func upgradeConfigSchema(oldVersion int, diskConfig *map[string]interface{}) err
|
||||
// The first schema upgrade:
|
||||
// No more "dnsfilter.txt", filters are now kept in data/filters/
|
||||
func upgradeSchema0to1(diskConfig *map[string]interface{}) error {
|
||||
log.Printf("%s(): called", util.FuncName())
|
||||
log.Printf("%s(): called", funcName())
|
||||
|
||||
dnsFilterPath := filepath.Join(Context.workDir, "dnsfilter.txt")
|
||||
if _, err := os.Stat(dnsFilterPath); !os.IsNotExist(err) {
|
||||
@@ -143,7 +143,7 @@ func upgradeSchema0to1(diskConfig *map[string]interface{}) error {
|
||||
// coredns is now dns in config
|
||||
// delete 'Corefile', since we don't use that anymore
|
||||
func upgradeSchema1to2(diskConfig *map[string]interface{}) error {
|
||||
log.Printf("%s(): called", util.FuncName())
|
||||
log.Printf("%s(): called", funcName())
|
||||
|
||||
coreFilePath := filepath.Join(Context.workDir, "Corefile")
|
||||
if _, err := os.Stat(coreFilePath); !os.IsNotExist(err) {
|
||||
@@ -167,7 +167,7 @@ func upgradeSchema1to2(diskConfig *map[string]interface{}) error {
|
||||
// Third schema upgrade:
|
||||
// Bootstrap DNS becomes an array
|
||||
func upgradeSchema2to3(diskConfig *map[string]interface{}) error {
|
||||
log.Printf("%s(): called", util.FuncName())
|
||||
log.Printf("%s(): called", funcName())
|
||||
|
||||
// Let's read dns configuration from diskConfig
|
||||
dnsConfig, ok := (*diskConfig)["dns"]
|
||||
@@ -204,7 +204,7 @@ func upgradeSchema2to3(diskConfig *map[string]interface{}) error {
|
||||
|
||||
// Add use_global_blocked_services=true setting for existing "clients" array
|
||||
func upgradeSchema3to4(diskConfig *map[string]interface{}) error {
|
||||
log.Printf("%s(): called", util.FuncName())
|
||||
log.Printf("%s(): called", funcName())
|
||||
|
||||
(*diskConfig)["schema_version"] = 4
|
||||
|
||||
@@ -240,7 +240,7 @@ func upgradeSchema3to4(diskConfig *map[string]interface{}) error {
|
||||
// password: "..."
|
||||
// ...
|
||||
func upgradeSchema4to5(diskConfig *map[string]interface{}) error {
|
||||
log.Printf("%s(): called", util.FuncName())
|
||||
log.Printf("%s(): called", funcName())
|
||||
|
||||
(*diskConfig)["schema_version"] = 5
|
||||
|
||||
@@ -295,7 +295,7 @@ func upgradeSchema4to5(diskConfig *map[string]interface{}) error {
|
||||
// - 127.0.0.1
|
||||
// - ...
|
||||
func upgradeSchema5to6(diskConfig *map[string]interface{}) error {
|
||||
log.Printf("%s(): called", util.FuncName())
|
||||
log.Printf("%s(): called", funcName())
|
||||
|
||||
(*diskConfig)["schema_version"] = 6
|
||||
|
||||
@@ -433,3 +433,12 @@ func upgradeSchema6to7(diskConfig *map[string]interface{}) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
||||
// package.
|
||||
func funcName() string {
|
||||
pc := make([]uintptr, 10) // at least 1 entry needed
|
||||
runtime.Callers(2, pc)
|
||||
f := runtime.FuncForPC(pc[0])
|
||||
return path.Base(f.Name())
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package home
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
golog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
@@ -15,11 +15,37 @@ import (
|
||||
"github.com/gobuffalo/packr"
|
||||
)
|
||||
|
||||
type WebConfig struct {
|
||||
const (
|
||||
// ReadTimeout is the maximum duration for reading the entire request,
|
||||
// including the body.
|
||||
ReadTimeout = 10 * time.Second
|
||||
|
||||
// ReadHeaderTimeout is the amount of time allowed to read request
|
||||
// headers.
|
||||
ReadHeaderTimeout = 10 * time.Second
|
||||
|
||||
// WriteTimeout is the maximum duration before timing out writes of the
|
||||
// response.
|
||||
WriteTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
type webConfig struct {
|
||||
firstRun bool
|
||||
BindHost string
|
||||
BindPort int
|
||||
PortHTTPS int
|
||||
|
||||
// ReadTimeout is an option to pass to http.Server for setting an
|
||||
// appropriate field.
|
||||
ReadTimeout time.Duration
|
||||
|
||||
// ReadHeaderTimeout is an option to pass to http.Server for setting an
|
||||
// appropriate field.
|
||||
ReadHeaderTimeout time.Duration
|
||||
|
||||
// WriteTimeout is an option to pass to http.Server for setting an
|
||||
// appropriate field.
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
|
||||
// HTTPSServer - HTTPS Server
|
||||
@@ -34,44 +60,30 @@ type HTTPSServer struct {
|
||||
|
||||
// Web - module object
|
||||
type Web struct {
|
||||
conf *WebConfig
|
||||
conf *webConfig
|
||||
forceHTTPS bool
|
||||
portHTTPS int
|
||||
httpServer *http.Server // HTTP module
|
||||
httpsServer HTTPSServer // HTTPS module
|
||||
errLogger *golog.Logger
|
||||
}
|
||||
|
||||
// Proxy between Go's "log" and "golibs/log"
|
||||
type logWriter struct {
|
||||
}
|
||||
|
||||
// HTTP server calls this function to log an error
|
||||
func (w *logWriter) Write(p []byte) (int, error) {
|
||||
log.Debug("Web: %s", string(p))
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// CreateWeb - create module
|
||||
func CreateWeb(conf *WebConfig) *Web {
|
||||
func CreateWeb(conf *webConfig) *Web {
|
||||
log.Info("Initialize web module")
|
||||
|
||||
w := Web{}
|
||||
w.conf = conf
|
||||
|
||||
lw := logWriter{}
|
||||
w.errLogger = golog.New(&lw, "", 0)
|
||||
|
||||
// Initialize and run the admin Web interface
|
||||
box := packr.NewBox("../../build/static")
|
||||
|
||||
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
|
||||
http.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box)))))
|
||||
Context.mux.Handle("/", postInstallHandler(optionalAuthHandler(gziphandler.GzipHandler(http.FileServer(box)))))
|
||||
|
||||
// add handlers for /install paths, we only need them when we're not configured yet
|
||||
if conf.firstRun {
|
||||
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
|
||||
http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
|
||||
Context.mux.Handle("/install.html", preInstallHandler(http.FileServer(box)))
|
||||
w.registerInstallHandlers()
|
||||
} else {
|
||||
registerControlHandlers()
|
||||
@@ -139,9 +151,14 @@ func (web *Web) Start() {
|
||||
// we need to have new instance, because after Shutdown() the Server is not usable
|
||||
address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.BindPort))
|
||||
web.httpServer = &http.Server{
|
||||
ErrorLog: web.errLogger,
|
||||
Addr: address,
|
||||
ErrorLog: log.StdLog("web: http", log.DEBUG),
|
||||
Addr: address,
|
||||
Handler: withMiddlewares(Context.mux, limitRequestBody),
|
||||
ReadTimeout: web.conf.ReadTimeout,
|
||||
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
||||
WriteTimeout: web.conf.WriteTimeout,
|
||||
}
|
||||
|
||||
err := web.httpServer.ListenAndServe()
|
||||
if err != http.ErrServerClosed {
|
||||
cleanupAlways()
|
||||
@@ -189,7 +206,7 @@ func (web *Web) tlsServerLoop() {
|
||||
// prepare HTTPS server
|
||||
address := net.JoinHostPort(web.conf.BindHost, strconv.Itoa(web.conf.PortHTTPS))
|
||||
web.httpsServer.server = &http.Server{
|
||||
ErrorLog: web.errLogger,
|
||||
ErrorLog: log.StdLog("web: https", log.DEBUG),
|
||||
Addr: address,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{web.httpsServer.cert},
|
||||
@@ -197,6 +214,10 @@ func (web *Web) tlsServerLoop() {
|
||||
RootCAs: Context.tlsRoots,
|
||||
CipherSuites: Context.tlsCiphers,
|
||||
},
|
||||
Handler: Context.mux,
|
||||
ReadTimeout: web.conf.ReadTimeout,
|
||||
ReadHeaderTimeout: web.conf.ReadHeaderTimeout,
|
||||
WriteTimeout: web.conf.WriteTimeout,
|
||||
}
|
||||
|
||||
printHTTPAddresses("https")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/util"
|
||||
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
@@ -115,6 +116,9 @@ func whoisParse(data string) map[string]string {
|
||||
return m
|
||||
}
|
||||
|
||||
// MaxConnReadSize is an upper limit in bytes for reading from net.Conn.
|
||||
const MaxConnReadSize = 64 * 1024
|
||||
|
||||
// Send request to a server and receive the response
|
||||
func (w *Whois) query(target, serverAddr string) (string, error) {
|
||||
addr, _, _ := net.SplitHostPort(serverAddr)
|
||||
@@ -127,13 +131,20 @@ func (w *Whois) query(target, serverAddr string) (string, error) {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
connReadCloser, err := aghio.LimitReadCloser(conn, MaxConnReadSize)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer connReadCloser.Close()
|
||||
|
||||
_ = conn.SetReadDeadline(time.Now().Add(time.Duration(w.timeoutMsec) * time.Millisecond))
|
||||
_, err = conn.Write([]byte(target + "\r\n"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(conn)
|
||||
// This use of ReadAll is now safe, because we limited the conn Reader.
|
||||
data, err := ioutil.ReadAll(connReadCloser)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package querylog
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,86 +12,219 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// decodeLogEntry - decodes query log entry from a line
|
||||
// nolint (gocyclo)
|
||||
func decodeLogEntry(ent *logEntry, str string) {
|
||||
var b bool
|
||||
var i int
|
||||
var err error
|
||||
for {
|
||||
k, v, t := readJSON(&str)
|
||||
if t == jsonTErr {
|
||||
break
|
||||
type logEntryHandler (func(t json.Token, ent *logEntry) error)
|
||||
|
||||
var logEntryHandlers = map[string]logEntryHandler{
|
||||
"IP": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
switch k {
|
||||
case "IP":
|
||||
if len(ent.IP) == 0 {
|
||||
ent.IP = v
|
||||
}
|
||||
case "T":
|
||||
ent.Time, err = time.Parse(time.RFC3339, v)
|
||||
|
||||
case "QH":
|
||||
ent.QHost = v
|
||||
case "QT":
|
||||
ent.QType = v
|
||||
case "QC":
|
||||
ent.QClass = v
|
||||
|
||||
case "CP":
|
||||
ent.ClientProto, err = NewClientProto(v)
|
||||
|
||||
case "Answer":
|
||||
ent.Answer, err = base64.StdEncoding.DecodeString(v)
|
||||
case "OrigAnswer":
|
||||
ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v)
|
||||
|
||||
case "IsFiltered":
|
||||
b, err = strconv.ParseBool(v)
|
||||
ent.Result.IsFiltered = b
|
||||
case "Rule":
|
||||
ent.Result.Rule = v
|
||||
case "FilterID":
|
||||
i, err = strconv.Atoi(v)
|
||||
ent.Result.FilterID = int64(i)
|
||||
case "Reason":
|
||||
i, err = strconv.Atoi(v)
|
||||
ent.Result.Reason = dnsfilter.Reason(i)
|
||||
case "ServiceName":
|
||||
ent.Result.ServiceName = v
|
||||
|
||||
case "Upstream":
|
||||
ent.Upstream = v
|
||||
case "Elapsed":
|
||||
i, err = strconv.Atoi(v)
|
||||
ent.Elapsed = time.Duration(i)
|
||||
|
||||
// pre-v0.99.3 compatibility:
|
||||
case "Question":
|
||||
var qstr []byte
|
||||
qstr, err = base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
q := new(dns.Msg)
|
||||
err = q.Unpack(qstr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
ent.QHost = q.Question[0].Name
|
||||
if len(ent.QHost) == 0 {
|
||||
break
|
||||
}
|
||||
ent.QHost = ent.QHost[:len(ent.QHost)-1]
|
||||
ent.QType = dns.TypeToString[q.Question[0].Qtype]
|
||||
ent.QClass = dns.ClassToString[q.Question[0].Qclass]
|
||||
case "Time":
|
||||
ent.Time, err = time.Parse(time.RFC3339, v)
|
||||
if len(ent.IP) == 0 {
|
||||
ent.IP = v
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
"T": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
ent.Time, err = time.Parse(time.RFC3339, v)
|
||||
return err
|
||||
},
|
||||
"QH": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ent.QHost = v
|
||||
return nil
|
||||
},
|
||||
"QT": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ent.QType = v
|
||||
return nil
|
||||
},
|
||||
"QC": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ent.QClass = v
|
||||
return nil
|
||||
},
|
||||
"CP": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
ent.ClientProto, err = NewClientProto(v)
|
||||
return err
|
||||
},
|
||||
"Answer": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
ent.Answer, err = base64.StdEncoding.DecodeString(v)
|
||||
return err
|
||||
},
|
||||
"OrigAnswer": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
ent.OrigAnswer, err = base64.StdEncoding.DecodeString(v)
|
||||
return err
|
||||
},
|
||||
"IsFiltered": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(bool)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ent.Result.IsFiltered = v
|
||||
return nil
|
||||
},
|
||||
"Rule": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ent.Result.Rule = v
|
||||
return nil
|
||||
},
|
||||
"FilterID": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(json.Number)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
i, err := v.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ent.Result.FilterID = i
|
||||
return nil
|
||||
},
|
||||
"Reason": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(json.Number)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
i, err := v.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ent.Result.Reason = dnsfilter.Reason(i)
|
||||
return nil
|
||||
},
|
||||
"ServiceName": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ent.Result.ServiceName = v
|
||||
return nil
|
||||
},
|
||||
"Upstream": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ent.Upstream = v
|
||||
return nil
|
||||
},
|
||||
"Elapsed": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(json.Number)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
i, err := v.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ent.Elapsed = time.Duration(i)
|
||||
return nil
|
||||
},
|
||||
"Result": func(json.Token, *logEntry) error {
|
||||
return nil
|
||||
},
|
||||
"Question": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var qstr []byte
|
||||
qstr, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := new(dns.Msg)
|
||||
err = q.Unpack(qstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ent.QHost = q.Question[0].Name
|
||||
if len(ent.QHost) == 0 {
|
||||
return nil // nil???
|
||||
}
|
||||
ent.QHost = ent.QHost[:len(ent.QHost)-1]
|
||||
ent.QType = dns.TypeToString[q.Question[0].Qtype]
|
||||
ent.QClass = dns.ClassToString[q.Question[0].Qclass]
|
||||
return nil
|
||||
},
|
||||
"Time": func(t json.Token, ent *logEntry) error {
|
||||
v, ok := t.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
ent.Time, err = time.Parse(time.RFC3339, v)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
func decodeLogEntry(ent *logEntry, str string) {
|
||||
dec := json.NewDecoder(strings.NewReader(str))
|
||||
dec.UseNumber()
|
||||
for {
|
||||
keyToken, err := dec.Token()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Debug("decodeLogEntry err: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if _, ok := keyToken.(json.Delim); ok {
|
||||
continue
|
||||
}
|
||||
|
||||
key, ok := keyToken.(string)
|
||||
if !ok {
|
||||
log.Debug("decodeLogEntry: keyToken is %T and not string", keyToken)
|
||||
return
|
||||
}
|
||||
|
||||
handler, ok := logEntryHandlers[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
val, err := dec.Token()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = handler(val, ent); err != nil {
|
||||
log.Debug("decodeLogEntry err: %s", err)
|
||||
break
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,72 +243,3 @@ func readJSONValue(s, name string) string {
|
||||
end := start + i
|
||||
return s[start:end]
|
||||
}
|
||||
|
||||
const (
|
||||
jsonTErr = iota
|
||||
jsonTObj
|
||||
jsonTStr
|
||||
jsonTNum
|
||||
jsonTBool
|
||||
)
|
||||
|
||||
// Parse JSON key-value pair
|
||||
// e.g.: "key":VALUE where VALUE is "string", true|false (boolean), or 123.456 (number)
|
||||
// Note the limitations:
|
||||
// . doesn't support whitespace
|
||||
// . doesn't support "null"
|
||||
// . doesn't validate boolean or number
|
||||
// . no proper handling of {} braces
|
||||
// . no handling of [] brackets
|
||||
// Return (key, value, type)
|
||||
func readJSON(ps *string) (string, string, int32) {
|
||||
s := *ps
|
||||
k := ""
|
||||
v := ""
|
||||
t := int32(jsonTErr)
|
||||
|
||||
q1 := strings.IndexByte(s, '"')
|
||||
if q1 == -1 {
|
||||
return k, v, t
|
||||
}
|
||||
q2 := strings.IndexByte(s[q1+1:], '"')
|
||||
if q2 == -1 {
|
||||
return k, v, t
|
||||
}
|
||||
k = s[q1+1 : q1+1+q2]
|
||||
s = s[q1+1+q2+1:]
|
||||
|
||||
if len(s) < 2 || s[0] != ':' {
|
||||
return k, v, t
|
||||
}
|
||||
|
||||
if s[1] == '"' {
|
||||
q2 = strings.IndexByte(s[2:], '"')
|
||||
if q2 == -1 {
|
||||
return k, v, t
|
||||
}
|
||||
v = s[2 : 2+q2]
|
||||
t = jsonTStr
|
||||
s = s[2+q2+1:]
|
||||
|
||||
} else if s[1] == '{' {
|
||||
t = jsonTObj
|
||||
s = s[1+1:]
|
||||
|
||||
} else {
|
||||
sep := strings.IndexAny(s[1:], ",}")
|
||||
if sep == -1 {
|
||||
return k, v, t
|
||||
}
|
||||
v = s[1 : 1+sep]
|
||||
if s[1] == 't' || s[1] == 'f' {
|
||||
t = jsonTBool
|
||||
} else if s[1] == '.' || (s[1] >= '0' && s[1] <= '9') {
|
||||
t = jsonTNum
|
||||
}
|
||||
s = s[1+sep+1:]
|
||||
}
|
||||
|
||||
*ps = s
|
||||
return k, v, t
|
||||
}
|
||||
|
||||
@@ -32,6 +32,66 @@ func TestDecode_decodeQueryLog(t *testing.T) {
|
||||
name: "back_compatibility_bad_decoding",
|
||||
log: `{"Question":"LgBAAABAAAAAAAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAAB","Answer":"ULiBgAABAAAAAQAAC2FkZ3VhcmR0ZWFtBmdpdGh1YgJpbwAAHAABwBgABgABAAADQgBLB25zLTE2MjIJYXdzZG5zLTEwAmNvAnVrABFhd3NkbnMtaG9zdG1hc3RlcgZhbWF6b24DY29tAAAAAAEAABwgAAADhAASdQAAAVGA","Result":{},"Time":"2020-11-13T12:41:25.970861+03:00","Elapsed":244066501,"IP":"127.0.0.1","Upstream":"https://1.1.1.1:443/dns-query"}`,
|
||||
want: "decodeLogEntry err: illegal base64 data at input byte 48\n",
|
||||
}, {
|
||||
name: "modern_all_right",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_filter_id",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1.5},"Elapsed":837429}`,
|
||||
want: "decodeLogEntry err: strconv.ParseInt: parsing \"1.5\": invalid syntax\n",
|
||||
}, {
|
||||
name: "bad_is_filtered",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":trooe,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_elapsed",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":-1}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_ip",
|
||||
log: `{"IP":127001,"T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_time",
|
||||
log: `{"IP":"127.0.0.1","T":"12/09/1998T15:00:00.000000+05:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "decodeLogEntry err: parsing time \"12/09/1998T15:00:00.000000+05:00\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"9/1998T15:00:00.000000+05:00\" as \"2006\"\n",
|
||||
}, {
|
||||
name: "bad_host",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":6,"QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_type",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":true,"QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_class",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":false,"CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_client_proto",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":8,"Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "very_bad_client_proto",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"dog","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "decodeLogEntry err: invalid client proto: \"dog\"\n",
|
||||
}, {
|
||||
name: "bad_answer",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":0.9,"Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "very_bad_answer",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "decodeLogEntry err: illegal base64 data at input byte 61\n",
|
||||
}, {
|
||||
name: "bad_rule",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":3,"Rule":false,"FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}, {
|
||||
name: "bad_reason",
|
||||
log: `{"IP":"127.0.0.1","T":"2020-11-25T18:55:56.519796+03:00","QH":"an.yandex.ru","QT":"A","QC":"IN","CP":"","Answer":"Qz+BgAABAAEAAAAAAmFuBnlhbmRleAJydQAAAQABwAwAAQABAAAACgAEAAAAAA==","Result":{"IsFiltered":true,"Reason":true,"Rule":"||an.yandex.","FilterID":1},"Elapsed":837429}`,
|
||||
want: "default",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -48,30 +108,3 @@ func TestDecode_decodeQueryLog(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
s := `
|
||||
{"keystr":"val","obj":{"keybool":true,"keyint":123456}}
|
||||
`
|
||||
k, v, jtype := readJSON(&s)
|
||||
assert.Equal(t, jtype, int32(jsonTStr))
|
||||
assert.Equal(t, "keystr", k)
|
||||
assert.Equal(t, "val", v)
|
||||
|
||||
k, _, jtype = readJSON(&s)
|
||||
assert.Equal(t, jtype, int32(jsonTObj))
|
||||
assert.Equal(t, "obj", k)
|
||||
|
||||
k, v, jtype = readJSON(&s)
|
||||
assert.Equal(t, jtype, int32(jsonTBool))
|
||||
assert.Equal(t, "keybool", k)
|
||||
assert.Equal(t, "true", v)
|
||||
|
||||
k, v, jtype = readJSON(&s)
|
||||
assert.Equal(t, jtype, int32(jsonTNum))
|
||||
assert.Equal(t, "keyint", k)
|
||||
assert.Equal(t, "123456", v)
|
||||
|
||||
_, _, jtype = readJSON(&s)
|
||||
assert.True(t, jtype == jsonTErr)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) {
|
||||
entries, oldest := l.search(params)
|
||||
|
||||
// convert log entries to JSON
|
||||
var data = l.entriesToJSON(entries, oldest)
|
||||
data := l.entriesToJSON(entries, oldest)
|
||||
|
||||
jsonVal, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
@@ -32,7 +32,7 @@ func (l *queryLog) getClientIP(clientIP string) string {
|
||||
// entriesToJSON - converts log entries to JSON
|
||||
func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[string]interface{} {
|
||||
// init the response object
|
||||
var data = []map[string]interface{}{}
|
||||
data := []map[string]interface{}{}
|
||||
|
||||
// the elements order is already reversed (from newer to older)
|
||||
for i := 0; i < len(entries); i++ {
|
||||
@@ -41,7 +41,7 @@ func (l *queryLog) entriesToJSON(entries []*logEntry, oldest time.Time) map[stri
|
||||
data = append(data, jsonEntry)
|
||||
}
|
||||
|
||||
var result = map[string]interface{}{}
|
||||
result := map[string]interface{}{}
|
||||
result["oldest"] = ""
|
||||
if !oldest.IsZero() {
|
||||
result["oldest"] = oldest.Format(time.RFC3339Nano)
|
||||
@@ -123,7 +123,7 @@ func answerToMap(a *dns.Msg) []map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var answers = []map[string]interface{}{}
|
||||
answers := []map[string]interface{}{}
|
||||
for _, k := range a.Answer {
|
||||
header := k.Header()
|
||||
answer := map[string]interface{}{
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package querylog
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/proxyutil"
|
||||
|
||||
@@ -20,7 +23,7 @@ func TestMain(m *testing.M) {
|
||||
func prepareTestDir() string {
|
||||
const dir = "./agh-test"
|
||||
_ = os.RemoveAll(dir)
|
||||
_ = os.MkdirAll(dir, 0755)
|
||||
_ = os.MkdirAll(dir, 0o755)
|
||||
return dir
|
||||
}
|
||||
|
||||
@@ -263,3 +266,72 @@ func assertLogEntry(t *testing.T, entry *logEntry, host, answer, client string)
|
||||
assert.Equal(t, answer, ip.String())
|
||||
return true
|
||||
}
|
||||
|
||||
func testEntries() (entries []*logEntry) {
|
||||
rsrc := rand.NewSource(time.Now().UnixNano())
|
||||
rgen := rand.New(rsrc)
|
||||
|
||||
entries = make([]*logEntry, 1000)
|
||||
for i := range entries {
|
||||
min := rgen.Intn(60)
|
||||
sec := rgen.Intn(60)
|
||||
entries[i] = &logEntry{
|
||||
Time: time.Date(2020, 1, 1, 0, min, sec, 0, time.UTC),
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// logEntriesByTimeDesc is a wrapper over []*logEntry for sorting.
|
||||
//
|
||||
// NOTE(a.garipov): Weirdly enough, on my machine this gets consistently
|
||||
// outperformed by sort.Slice, see the benchmark below. I'm leaving this
|
||||
// implementation here, in tests, in case we want to make sure it outperforms on
|
||||
// most machines, but for now this is unused in the actual code.
|
||||
type logEntriesByTimeDesc []*logEntry
|
||||
|
||||
// Len implements the sort.Interface interface for logEntriesByTimeDesc.
|
||||
func (les logEntriesByTimeDesc) Len() (n int) { return len(les) }
|
||||
|
||||
// Less implements the sort.Interface interface for logEntriesByTimeDesc.
|
||||
func (les logEntriesByTimeDesc) Less(i, j int) (less bool) {
|
||||
return les[i].Time.After(les[j].Time)
|
||||
}
|
||||
|
||||
// Swap implements the sort.Interface interface for logEntriesByTimeDesc.
|
||||
func (les logEntriesByTimeDesc) Swap(i, j int) { les[i], les[j] = les[j], les[i] }
|
||||
|
||||
func BenchmarkLogEntry_sort(b *testing.B) {
|
||||
b.Run("methods", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
entries := testEntries()
|
||||
b.StartTimer()
|
||||
|
||||
sort.Stable(logEntriesByTimeDesc(entries))
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("reflect", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
entries := testEntries()
|
||||
b.StartTimer()
|
||||
|
||||
sort.SliceStable(entries, func(i, j int) (less bool) {
|
||||
return entries[i].Time.After(entries[j].Time)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogEntriesByTime_sort(t *testing.T) {
|
||||
entries := testEntries()
|
||||
sort.Sort(logEntriesByTimeDesc(entries))
|
||||
|
||||
for i := 1; i < len(entries); i++ {
|
||||
assert.False(t, entries[i].Time.After(entries[i-1].Time),
|
||||
"%s %s", entries[i].Time, entries[i-1].Time)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func NewQLogFile(path string) (*QLogFile, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Seek performs binary search in the query log file looking for a record
|
||||
// SeekTS performs binary search in the query log file looking for a record
|
||||
// with the specified timestamp. Once the record is found, it sets
|
||||
// "position" so that the next ReadNext call returned that record.
|
||||
//
|
||||
@@ -70,7 +70,7 @@ func NewQLogFile(path string) (*QLogFile, error) {
|
||||
// so that when we call "ReadNext" this line was returned.
|
||||
// * Depth of the search (how many times we compared timestamps).
|
||||
// * If we could not find it, it returns one of the errors described above.
|
||||
func (q *QLogFile) Seek(timestamp int64) (int64, int, error) {
|
||||
func (q *QLogFile) SeekTS(timestamp int64) (int64, int, error) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
@@ -61,7 +61,7 @@ func TestQLogFileLarge(t *testing.T) {
|
||||
line, err = q.ReadNext()
|
||||
if err == nil {
|
||||
assert.True(t, len(line) > 0)
|
||||
read += 1
|
||||
read++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,12 +96,12 @@ func TestQLogFileSeekLargeFile(t *testing.T) {
|
||||
testSeekLineQLogFile(t, q, count)
|
||||
|
||||
// CASE 5: Seek non-existent (too low)
|
||||
_, _, err = q.Seek(123)
|
||||
_, _, err = q.SeekTS(123)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// CASE 6: Seek non-existent (too high)
|
||||
ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00")
|
||||
_, _, err = q.Seek(ts.UnixNano())
|
||||
_, _, err = q.SeekTS(ts.UnixNano())
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// CASE 7: "Almost" found
|
||||
@@ -110,7 +110,7 @@ func TestQLogFileSeekLargeFile(t *testing.T) {
|
||||
// ALMOST the record we need
|
||||
timestamp := readQLogTimestamp(line) - 1
|
||||
assert.NotEqual(t, uint64(0), timestamp)
|
||||
_, depth, err := q.Seek(timestamp)
|
||||
_, depth, err := q.SeekTS(timestamp)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, depth <= int(math.Log2(float64(count))+3))
|
||||
}
|
||||
@@ -142,12 +142,12 @@ func TestQLogFileSeekSmallFile(t *testing.T) {
|
||||
testSeekLineQLogFile(t, q, count)
|
||||
|
||||
// CASE 5: Seek non-existent (too low)
|
||||
_, _, err = q.Seek(123)
|
||||
_, _, err = q.SeekTS(123)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// CASE 6: Seek non-existent (too high)
|
||||
ts, _ := time.Parse(time.RFC3339, "2100-01-02T15:04:05Z07:00")
|
||||
_, _, err = q.Seek(ts.UnixNano())
|
||||
_, _, err = q.SeekTS(ts.UnixNano())
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// CASE 7: "Almost" found
|
||||
@@ -156,7 +156,7 @@ func TestQLogFileSeekSmallFile(t *testing.T) {
|
||||
// ALMOST the record we need
|
||||
timestamp := readQLogTimestamp(line) - 1
|
||||
assert.NotEqual(t, uint64(0), timestamp)
|
||||
_, depth, err := q.Seek(timestamp)
|
||||
_, depth, err := q.SeekTS(timestamp)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, depth <= int(math.Log2(float64(count))+3))
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func testSeekLineQLogFile(t *testing.T, q *QLogFile, lineNumber int) {
|
||||
assert.NotEqual(t, uint64(0), ts)
|
||||
|
||||
// try seeking to that line now
|
||||
pos, _, err := q.Seek(ts)
|
||||
pos, _, err := q.SeekTS(ts)
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, int64(0), pos)
|
||||
|
||||
@@ -249,7 +249,7 @@ func prepareTestFiles(dir string, filesCount, linesCount int) []string {
|
||||
files[filesCount-j-1] = f.Name()
|
||||
|
||||
for i := 0; i < linesCount; i++ {
|
||||
lineIP += 1
|
||||
lineIP++
|
||||
lineTime = lineTime.Add(time.Second)
|
||||
|
||||
ip := make(net.IP, 4)
|
||||
@@ -284,7 +284,7 @@ func TestQLogSeek(t *testing.T) {
|
||||
|
||||
target, _ := time.Parse(time.RFC3339, "2020-08-31T18:44:25.376690873+03:00")
|
||||
|
||||
_, depth, err := q.Seek(target.UnixNano())
|
||||
_, depth, err := q.SeekTS(target.UnixNano())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, depth)
|
||||
}
|
||||
@@ -313,7 +313,7 @@ func TestQLogSeek_ErrTSTooLate(t *testing.T) {
|
||||
target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:25.382540454+03:00")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, depth, err := q.Seek(target.UnixNano() + int64(time.Second))
|
||||
_, depth, err := q.SeekTS(target.UnixNano() + int64(time.Second))
|
||||
assert.Equal(t, ErrTSTooLate, err)
|
||||
assert.Equal(t, 2, depth)
|
||||
}
|
||||
@@ -342,7 +342,7 @@ func TestQLogSeek_ErrTSTooEarly(t *testing.T) {
|
||||
target, err := time.Parse(time.RFC3339, "2020-08-31T18:44:23.911246629+03:00")
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, depth, err := q.Seek(target.UnixNano() - int64(time.Second))
|
||||
_, depth, err := q.SeekTS(target.UnixNano() - int64(time.Second))
|
||||
assert.Equal(t, ErrTSTooEarly, err)
|
||||
assert.Equal(t, 1, depth)
|
||||
}
|
||||
@@ -44,16 +44,13 @@ func NewQLogReader(files []string) (*QLogReader, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Seek performs binary search of a query log record with the specified timestamp.
|
||||
// If the record is found, it sets QLogReader's position to point to that line,
|
||||
// so that the next ReadNext call returned this line.
|
||||
//
|
||||
// Returns nil if the record is successfully found.
|
||||
// Returns an error if for some reason we could not find a record with the specified timestamp.
|
||||
func (r *QLogReader) Seek(timestamp int64) (err error) {
|
||||
// SeekTS performs binary search of a query log record with the specified
|
||||
// timestamp. If the record is found, it sets QLogReader's position to point to
|
||||
// that line, so that the next ReadNext call returned this line.
|
||||
func (r *QLogReader) SeekTS(timestamp int64) (err error) {
|
||||
for i := len(r.qFiles) - 1; i >= 0; i-- {
|
||||
q := r.qFiles[i]
|
||||
_, _, err = q.Seek(timestamp)
|
||||
_, _, err = q.SeekTS(timestamp)
|
||||
if err == nil {
|
||||
// Search is finished, and the searched element have
|
||||
// been found. Update currentFile only, position is
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user