mirror of https://github.com/0xERR0R/blocky.git
Merge branch 'development'
This commit is contained in:
commit
6c616898b8
|
@ -1,6 +1,13 @@
|
|||
bin/
|
||||
dist/
|
||||
bin
|
||||
dist
|
||||
site
|
||||
docs
|
||||
node_modules
|
||||
.git
|
||||
.idea
|
||||
.github
|
||||
testdata/
|
||||
node_modules/
|
||||
.vscode
|
||||
.gitignore
|
||||
*.md
|
||||
LICENSE
|
||||
vendor
|
|
@ -7,3 +7,8 @@ updates:
|
|||
open-pull-requests-limit: 10
|
||||
assignees:
|
||||
- 0xERR0R
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
|
|
|
@ -1,45 +1,35 @@
|
|||
name: CI Build
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
make:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
make: [build, test, race, docker-build, goreleaser]
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version-file: go.mod
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
if [ -f Gopkg.toml ]; then
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure
|
||||
fi
|
||||
run: go mod download
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
- name: Race detection
|
||||
run: make race
|
||||
- name: make ${{ matrix.make }}
|
||||
run: make ${{ matrix.make }}
|
||||
if: matrix.make != 'goreleaser'
|
||||
|
||||
- name: Upload results to codecov
|
||||
run: bash <(curl -s https://codecov.io/bash) -t 48d6a1a8-a66e-4f27-9cc1-a7b91c4209b2
|
||||
|
||||
- name: Docker images
|
||||
run: make docker-build
|
||||
uses: codecov/codecov-action@v3
|
||||
if: matrix.make == 'test'
|
||||
|
||||
- name: Check GoReleaser configuration
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
if: matrix.make == 'goreleaser'
|
||||
with:
|
||||
args: check
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == '0xERR0R'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v6
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
|
||||
days-before-issue-stale: 90
|
||||
days-before-pr-stale: 45
|
||||
days-before-issue-close: 5
|
||||
days-before-pr-close: 10
|
||||
exempt-all-milestones: true
|
||||
operations-per-run: 60
|
|
@ -35,11 +35,11 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -50,7 +50,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
@ -64,4 +64,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
|
|
@ -1,55 +1,207 @@
|
|||
name: Development docker build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- development
|
||||
- fb-*
|
||||
|
||||
permissions:
|
||||
security-events: write
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
if: github.repository_owner == '0xERR0R'
|
||||
check:
|
||||
name: Check if workflow should run
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
enabled: ${{ steps.check.outputs.enabled }}
|
||||
steps:
|
||||
- name: Enabled Check
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
ENABLED=${{ secrets.DEVELOPMENT_DOCKER }}
|
||||
|
||||
if [[ "${{ github.repository_owner }}" == "0xERR0R" ]]; then
|
||||
ENABLED="true"
|
||||
fi
|
||||
|
||||
if [[ "${ENABLED,,}" != "true" ]]; then
|
||||
echo "enabled=0" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Workflow is disabled"
|
||||
|
||||
echo "### Workflow is disabled" >> $GITHUB_STEP_SUMMARY
|
||||
echo "To enable this workflow by creating a secret 'DEVELOPMENT_DOCKER' with the value 'true'" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "enabled=1" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Workflow is enabled"
|
||||
fi
|
||||
|
||||
docker:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
needs: check
|
||||
if: ${{ needs.check.outputs.enabled == 1 }}
|
||||
outputs:
|
||||
repository: ${{ steps.get_vars.outputs.repository }}
|
||||
branch: ${{ steps.get_vars.outputs.branch }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: arm,arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Get registry token
|
||||
id: get_token
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ secrets.CR_PAT }}" ]; then
|
||||
echo "token=${{ secrets.CR_PAT }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "token=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
password: ${{ steps.get_token.outputs.token }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository_owner == '0xERR0R'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Extract branch name
|
||||
|
||||
- name: Populate build variables
|
||||
id: get_vars
|
||||
shell: bash
|
||||
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
|
||||
id: extract_branch
|
||||
run: |
|
||||
REPOSITORY=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
|
||||
echo "repository=${REPOSITORY}" >> $GITHUB_OUTPUT
|
||||
echo "REPOSITORY: ${REPOSITORY}"
|
||||
|
||||
BRANCH=${GITHUB_REF#refs/heads/}
|
||||
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
|
||||
echo "Branch: ${BRANCH}"
|
||||
|
||||
VERSION=$(git describe --always --tags)
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION: ${VERSION}"
|
||||
|
||||
BUILD_TIME=$(date '+%Y%m%d-%H%M%S')
|
||||
echo "build_time=${BUILD_TIME}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_TIME: ${BUILD_TIME}"
|
||||
|
||||
TAGS="ghcr.io/${REPOSITORY}:${BRANCH}"
|
||||
if [[ "${{ github.repository_owner }}" == "0xERR0R" ]]; then
|
||||
TAGS="${TAGS} , spx01/blocky:${BRANCH}"
|
||||
fi
|
||||
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||
echo "TAGS: ${TAGS}"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/0xerr0r/blocky:${{ steps.extract_branch.outputs.branch }}
|
||||
spx01/blocky:${{ steps.extract_branch.outputs.branch }}
|
||||
- name: Scan image
|
||||
uses: anchore/scan-action@v3
|
||||
id: scan
|
||||
tags: ${{ steps.get_vars.outputs.tags }}
|
||||
build-args: |
|
||||
VERSION=${{ steps.get_vars.outputs.version }}
|
||||
BUILD_TIME=${{ steps.get_vars.outputs.build_time }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
repo-scan:
|
||||
name: Repo vulnerability scan
|
||||
runs-on: ubuntu-latest
|
||||
needs: check
|
||||
if: needs.check.outputs.enabled == 1
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run Trivy vulnerability scanner in repo mode
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image: "spx01/blocky:${{ steps.extract_branch.outputs.branch }}"
|
||||
fail-build: false
|
||||
acs-report-enable: true
|
||||
- name: upload Anchore scan SARIF report
|
||||
uses: github/codeql-action/upload-sarif@v1
|
||||
scan-type: 'fs'
|
||||
ignore-unfixed: true
|
||||
format: 'sarif'
|
||||
output: 'trivy-repo-results.sarif'
|
||||
severity: 'CRITICAL'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: ${{ steps.scan.outputs.sarif }}
|
||||
sarif_file: 'trivy-repo-results.sarif'
|
||||
|
||||
image-scan:
|
||||
name: Image vulnerability scan
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Trivy vulnerability scanner on Docker image
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: 'ghcr.io/${{ needs.docker.outputs.repository }}:${{ needs.docker.outputs.branch }}'
|
||||
format: 'sarif'
|
||||
output: 'trivy-image-results.sarif'
|
||||
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: 'trivy-image-results.sarif'
|
||||
|
||||
image-test:
|
||||
name: Test docker images
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: arm,arm64
|
||||
|
||||
- name: Test images
|
||||
shell: bash
|
||||
run: |
|
||||
echo '::group::Version for linux/amd64'
|
||||
docker run --rm ghcr.io/${{ needs.docker.outputs.repository }}:${{ needs.docker.outputs.branch }} version
|
||||
echo '::endgroup::'
|
||||
|
||||
echo '::group::Version for linux/arm/v6'
|
||||
docker run --platform linux/arm/v6 --rm ghcr.io/${{ needs.docker.outputs.repository }}:${{ needs.docker.outputs.branch }} version
|
||||
echo '::endgroup::'
|
||||
|
||||
echo '::group::Version for linux/arm/v7'
|
||||
docker run --platform linux/arm/v7 --rm ghcr.io/${{ needs.docker.outputs.repository }}:${{ needs.docker.outputs.branch }} version
|
||||
echo '::endgroup::'
|
||||
|
||||
echo '::group::Version for linux/arm64'
|
||||
docker run --platform linux/arm64 --rm ghcr.io/${{ needs.docker.outputs.repository }}:${{ needs.docker.outputs.branch }} version
|
||||
echo '::endgroup::'
|
|
@ -8,8 +8,8 @@ jobs:
|
|||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install mkdocs-material
|
||||
|
|
|
@ -9,12 +9,12 @@ jobs:
|
|||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.18'
|
||||
- uses: actions/checkout@v2
|
||||
go-version-file: go.mod
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: v1.46.2
|
||||
args: --timeout 5m0s
|
||||
run: make lint
|
|
@ -7,21 +7,20 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: github.repository_owner == '0xERR0R'
|
||||
steps:
|
||||
- name: Set up Go 1.18
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.18
|
||||
id: go
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
id: go
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
|
@ -30,40 +29,59 @@ jobs:
|
|||
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: crazy-max/ghaction-docker-meta@v1
|
||||
uses: crazy-max/ghaction-docker-meta@v4
|
||||
with:
|
||||
images: spx01/blocky,ghcr.io/0xerr0r/blocky
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: arm,arm64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Populate build variables
|
||||
id: get_vars
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(git describe --always --tags)
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "VERSION: ${VERSION}"
|
||||
|
||||
BUILD_TIME=$(date '+%Y%m%d-%H%M%S')
|
||||
echo "build_time=${BUILD_TIME}" >> $GITHUB_OUTPUT
|
||||
echo "BUILD_TIME: ${BUILD_TIME}"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
||||
build-args: |
|
||||
VERSION=${{ steps.get_vars.outputs.version }}
|
||||
BUILD_TIME=${{ steps.get_vars.outputs.build_time }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v3
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
/*.pem
|
||||
bin/
|
||||
|
@ -13,3 +14,5 @@ todo.txt
|
|||
!docs/config.yml
|
||||
node_modules
|
||||
package-lock.json
|
||||
.vscode/
|
||||
vendor/
|
|
@ -3,7 +3,6 @@ linters:
|
|||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
|
@ -30,7 +29,6 @@ linters:
|
|||
- gosimple
|
||||
- govet
|
||||
- grouper
|
||||
- ifshort
|
||||
- importas
|
||||
- ineffassign
|
||||
- lll
|
||||
|
@ -41,25 +39,24 @@ linters:
|
|||
- nilerr
|
||||
- nilnil
|
||||
- nlreturn
|
||||
- nolintlint
|
||||
- nosprintfhostport
|
||||
- prealloc
|
||||
- predeclared
|
||||
- revive
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- tenv
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
- wastedassign
|
||||
- whitespace
|
||||
- wsl
|
||||
disable:
|
||||
- noctx
|
||||
- contextcheck
|
||||
- scopelint
|
||||
|
||||
disable-all: false
|
||||
|
|
|
@ -45,6 +45,7 @@ snapshot:
|
|||
checksum:
|
||||
name_template: "{{ .ProjectName }}_checksums.txt"
|
||||
changelog:
|
||||
use: github
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
|
|
94
Dockerfile
94
Dockerfile
|
@ -1,43 +1,79 @@
|
|||
# build stage
|
||||
FROM golang:1.18-alpine AS build-env
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
make \
|
||||
gcc \
|
||||
libc-dev \
|
||||
zip \
|
||||
ca-certificates
|
||||
# ----------- stage: ca-certs
|
||||
# get newest certificates in seperate stage for caching
|
||||
FROM --platform=$BUILDPLATFORM alpine:3.16 AS ca-certs
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0
|
||||
|
||||
WORKDIR /src
|
||||
# update certificates and use the apk ones if update fails
|
||||
RUN --mount=type=cache,target=/etc/ssl/certs \
|
||||
update-ca-certificates 2>/dev/null || true
|
||||
|
||||
# ----------- stage: zig-env
|
||||
# zig compiler is used for CGO cross compilation
|
||||
# even though CGO is disabled it is used in the os and net package
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/euantorano/zig:master AS zig-env
|
||||
|
||||
# ----------- stage: build
|
||||
FROM --platform=$BUILDPLATFORM golang:1-alpine AS build
|
||||
|
||||
# required arguments
|
||||
ARG VERSION
|
||||
ARG BUILD_TIME
|
||||
|
||||
# auto provided by Docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
# set working directory
|
||||
WORKDIR /go/src
|
||||
|
||||
# download packages
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
RUN --mount=type=cache,target=/go/pkg \
|
||||
go mod download
|
||||
|
||||
# add source
|
||||
ADD . .
|
||||
COPY . .
|
||||
|
||||
ARG opts
|
||||
RUN env ${opts} make build
|
||||
# setup go & zig as CGO compiler
|
||||
COPY --from=zig-env /usr/local/bin/zig /usr/local/bin/zig
|
||||
ENV PATH="/usr/local/bin/zig:${PATH}" \
|
||||
CC="zigcc" \
|
||||
CXX="zigcpp" \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS="linux" \
|
||||
GOARCH=$TARGETARCH \
|
||||
GO_SKIP_GENERATE=1\
|
||||
GO_BUILD_FLAGS="-tags static -v " \
|
||||
BIN_USER=100\
|
||||
BIN_AUTOCAB=1 \
|
||||
BIN_OUT_DIR="/bin"
|
||||
|
||||
# final stage
|
||||
FROM alpine:3.16
|
||||
# add make & libcap
|
||||
RUN apk add --no-cache make libcap
|
||||
|
||||
# build binary
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
make build GOARM=${TARGETVARIANT##*v}
|
||||
|
||||
# ----------- stage: final
|
||||
FROM scratch
|
||||
|
||||
LABEL org.opencontainers.image.source="https://github.com/0xERR0R/blocky" \
|
||||
org.opencontainers.image.url="https://github.com/0xERR0R/blocky" \
|
||||
org.opencontainers.image.title="DNS proxy as ad-blocker for local network"
|
||||
|
||||
COPY --from=build-env /src/bin/blocky /app/blocky
|
||||
RUN apk add --no-cache ca-certificates bind-tools tini tzdata libcap && \
|
||||
adduser -S -D -H -h /app -s /sbin/nologin blocky && \
|
||||
setcap 'cap_net_bind_service=+ep' /app/blocky
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=3s CMD dig @127.0.0.1 -p 53 healthcheck.blocky +tcp +short || exit 1
|
||||
|
||||
USER blocky
|
||||
USER 100
|
||||
WORKDIR /app
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["sh", "-c", "/app/blocky --config ${CONFIG_FILE:-/app/config.yml}"]
|
||||
COPY --from=ca-certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /bin/blocky /app/blocky
|
||||
|
||||
ENV BLOCKY_CONFIG_FILE=/app/config.yml
|
||||
|
||||
ENTRYPOINT ["/app/blocky"]
|
||||
|
||||
HEALTHCHECK --interval=1m --timeout=3s CMD ["/app/blocky", "healthcheck"]
|
||||
|
|
69
Makefile
69
Makefile
|
@ -1,25 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
.PHONY: all clean build swagger test lint run fmt docker-build help
|
||||
.DEFAULT_GOAL:=help
|
||||
|
||||
.PHONY: all clean build swagger test lint run help
|
||||
.DEFAULT_GOAL := help
|
||||
VERSION?=$(shell git describe --always --tags)
|
||||
BUILD_TIME?=$(shell date '+%Y%m%d-%H%M%S')
|
||||
DOCKER_IMAGE_NAME=spx01/blocky
|
||||
|
||||
VERSION := $(shell git describe --always --tags)
|
||||
BUILD_TIME=$(shell date '+%Y%m%d-%H%M%S')
|
||||
DOCKER_IMAGE_NAME="spx01/blocky"
|
||||
BINARY_NAME=blocky
|
||||
BIN_OUT_DIR=bin
|
||||
BINARY_NAME:=blocky
|
||||
BIN_OUT_DIR?=bin
|
||||
|
||||
GOARCH?=$(shell go env GOARCH)
|
||||
GOARM?=$(shell go env GOARM)
|
||||
|
||||
GO_BUILD_FLAGS?=-v
|
||||
GO_BUILD_LD_FLAGS:=\
|
||||
-w \
|
||||
-s \
|
||||
-X github.com/0xERR0R/blocky/util.Version=${VERSION} \
|
||||
-X github.com/0xERR0R/blocky/util.BuildTime=${BUILD_TIME} \
|
||||
-X github.com/0xERR0R/blocky/util.Architecture=${GOARCH}${GOARM}
|
||||
|
||||
GO_BUILD_OUTPUT:=$(BIN_OUT_DIR)/$(BINARY_NAME)$(BINARY_SUFFIX)
|
||||
|
||||
export PATH=$(shell go env GOPATH)/bin:$(shell echo $$PATH)
|
||||
|
||||
all: build test lint ## Build binary (with tests)
|
||||
|
||||
clean: ## cleans output directory
|
||||
$(shell rm -rf $(BIN_OUT_DIR)/*)
|
||||
rm -rf $(BIN_OUT_DIR)/*
|
||||
|
||||
swagger: ## creates swagger documentation as html file
|
||||
go install github.com/swaggo/swag/cmd/swag@v1.6.9
|
||||
npm install bootprint bootprint-openapi html-inline
|
||||
$(shell go env GOPATH)/bin/swag init -g api/api.go
|
||||
go run github.com/swaggo/swag/cmd/swag init -g api/api.go
|
||||
$(shell) node_modules/bootprint/bin/bootprint.js openapi docs/swagger.json /tmp/swagger/
|
||||
$(shell) node_modules/html-inline/bin/cmd.js /tmp/swagger/index.html > docs/swagger.html
|
||||
|
||||
|
@ -27,19 +38,30 @@ serve_docs: ## serves online docs
|
|||
mkdocs serve
|
||||
|
||||
build: ## Build binary
|
||||
go install github.com/abice/go-enum@v0.4.0
|
||||
ifdef GO_SKIP_GENERATE
|
||||
$(info skipping go generate)
|
||||
else
|
||||
go generate ./...
|
||||
go build -v -ldflags="-w -s -X github.com/0xERR0R/blocky/util.Version=${VERSION} -X github.com/0xERR0R/blocky/util.BuildTime=${BUILD_TIME}" -o $(BIN_OUT_DIR)/$(BINARY_NAME)$(BINARY_SUFFIX)
|
||||
endif
|
||||
go build $(GO_BUILD_FLAGS) -ldflags="$(GO_BUILD_LD_FLAGS)" -o $(GO_BUILD_OUTPUT)
|
||||
ifdef BIN_USER
|
||||
$(info setting owner of $(GO_BUILD_OUTPUT) to $(BIN_USER))
|
||||
chown $(BIN_USER) $(GO_BUILD_OUTPUT)
|
||||
endif
|
||||
ifdef BIN_AUTOCAB
|
||||
$(info setting cap_net_bind_service to $(GO_BUILD_OUTPUT))
|
||||
setcap 'cap_net_bind_service=+ep' $(GO_BUILD_OUTPUT)
|
||||
endif
|
||||
|
||||
test: ## run tests
|
||||
go test -v -coverprofile=coverage.txt -covermode=atomic -cover ./...
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo -v --coverprofile=coverage.txt --covermode=atomic -cover ./...
|
||||
|
||||
race: ## run tests with race detector
|
||||
go test -race -short ./...
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo --race ./...
|
||||
|
||||
lint: build ## run golangcli-lint checks
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.46.2
|
||||
$(shell go env GOPATH)/bin/golangci-lint run
|
||||
lint: ## run golangcli-lint checks
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.50.1
|
||||
golangci-lint run --timeout 5m
|
||||
|
||||
run: build ## Build and run binary
|
||||
./$(BIN_OUT_DIR)/$(BINARY_NAME)
|
||||
|
@ -47,8 +69,15 @@ run: build ## Build and run binary
|
|||
fmt: ## gofmt and goimports all go files
|
||||
find . -name '*.go' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done
|
||||
|
||||
docker-build: ## Build docker image
|
||||
docker build --network=host --tag ${DOCKER_IMAGE_NAME} .
|
||||
docker-build: ## Build docker image
|
||||
go generate ./...
|
||||
docker buildx build \
|
||||
--build-arg VERSION=${VERSION} \
|
||||
--build-arg BUILD_TIME=${BUILD_TIME} \
|
||||
--network=host \
|
||||
-o type=docker \
|
||||
-t ${DOCKER_IMAGE_NAME} \
|
||||
.
|
||||
|
||||
help: ## Shows help
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
|
|
@ -12,6 +12,11 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
contentTypeHeader = "content-type"
|
||||
jsonContentType = "application/json"
|
||||
)
|
||||
|
||||
// BlockingControl interface to control the blocking status
|
||||
type BlockingControl interface {
|
||||
EnableBlocking()
|
||||
|
@ -57,7 +62,8 @@ func registerListRefreshEndpoints(router chi.Router, refresher ListRefresher) {
|
|||
// @Tags lists
|
||||
// @Success 200 "Lists were reloaded"
|
||||
// @Router /lists/refresh [post]
|
||||
func (l *ListRefreshEndpoint) apiListRefresh(_ http.ResponseWriter, _ *http.Request) {
|
||||
func (l *ListRefreshEndpoint) apiListRefresh(rw http.ResponseWriter, _ *http.Request) {
|
||||
rw.Header().Set(contentTypeHeader, jsonContentType)
|
||||
l.refresher.RefreshLists()
|
||||
}
|
||||
|
||||
|
@ -75,10 +81,12 @@ func registerBlockingEndpoints(router chi.Router, control BlockingControl) {
|
|||
// @Tags blocking
|
||||
// @Success 200 "Blocking is enabled"
|
||||
// @Router /blocking/enable [get]
|
||||
func (s *BlockingEndpoint) apiBlockingEnable(_ http.ResponseWriter, _ *http.Request) {
|
||||
func (s *BlockingEndpoint) apiBlockingEnable(rw http.ResponseWriter, _ *http.Request) {
|
||||
log.Log().Info("enabling blocking...")
|
||||
|
||||
s.control.EnableBlocking()
|
||||
|
||||
rw.Header().Set(contentTypeHeader, jsonContentType)
|
||||
}
|
||||
|
||||
// apiBlockingDisable is the http endpoint to disable the blocking status
|
||||
|
@ -98,6 +106,8 @@ func (s *BlockingEndpoint) apiBlockingDisable(rw http.ResponseWriter, req *http.
|
|||
err error
|
||||
)
|
||||
|
||||
rw.Header().Set(contentTypeHeader, jsonContentType)
|
||||
|
||||
// parse duration from query parameter
|
||||
durationParam := req.URL.Query().Get("duration")
|
||||
if len(durationParam) > 0 {
|
||||
|
@ -132,6 +142,8 @@ func (s *BlockingEndpoint) apiBlockingDisable(rw http.ResponseWriter, req *http.
|
|||
func (s *BlockingEndpoint) apiBlockingStatus(rw http.ResponseWriter, _ *http.Request) {
|
||||
status := s.control.BlockingStatus()
|
||||
|
||||
rw.Header().Set(contentTypeHeader, jsonContentType)
|
||||
|
||||
response, err := json.Marshal(status)
|
||||
util.LogOnError("unable to marshal response ", err)
|
||||
|
||||
|
|
|
@ -51,9 +51,9 @@ var _ = Describe("API tests", func() {
|
|||
r := &ListRefreshMock{}
|
||||
sut := &ListRefreshEndpoint{refresher: r}
|
||||
It("should trigger the list refresh", func() {
|
||||
httpCode, _ := DoGetRequest("/api/lists/refresh", sut.apiListRefresh)
|
||||
Expect(httpCode).Should(Equal(http.StatusOK))
|
||||
Expect(r.refreshTriggered).Should(BeTrue())
|
||||
resp, _ := DoGetRequest("/api/lists/refresh", sut.apiListRefresh)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -74,18 +74,18 @@ var _ = Describe("API tests", func() {
|
|||
It("should disable blocking resolver", func() {
|
||||
By("Calling Rest API to deactivate", func() {
|
||||
|
||||
httpCode, _ := DoGetRequest("/api/blocking/disable", sut.apiBlockingDisable)
|
||||
Expect(httpCode).Should(Equal(http.StatusOK))
|
||||
Expect(bc.enabled).Should(BeFalse())
|
||||
resp, _ := DoGetRequest("/api/blocking/disable", sut.apiBlockingDisable)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("Disable blocking is called with a wrong parameter", func() {
|
||||
It("Should return http bad request as return code", func() {
|
||||
httpCode, _ := DoGetRequest("/api/blocking/disable?duration=xyz", sut.apiBlockingDisable)
|
||||
|
||||
Expect(httpCode).Should(Equal(http.StatusBadRequest))
|
||||
resp, _ := DoGetRequest("/api/blocking/disable?duration=xyz", sut.apiBlockingDisable)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusBadRequest))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -96,8 +96,9 @@ var _ = Describe("API tests", func() {
|
|||
})
|
||||
|
||||
By("Calling Rest API to deactivate blocking for 0.5 sec", func() {
|
||||
httpCode, _ := DoGetRequest("/api/blocking/disable?duration=500ms", sut.apiBlockingDisable)
|
||||
Expect(httpCode).Should(Equal(http.StatusOK))
|
||||
resp, _ := DoGetRequest("/api/blocking/disable?duration=500ms", sut.apiBlockingDisable)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
})
|
||||
|
||||
By("ensure that the blocking is disabled", func() {
|
||||
|
@ -110,13 +111,15 @@ var _ = Describe("API tests", func() {
|
|||
When("Blocking status is called", func() {
|
||||
It("should return correct status", func() {
|
||||
By("enable blocking via API", func() {
|
||||
httpCode, _ := DoGetRequest("/api/blocking/enable", sut.apiBlockingEnable)
|
||||
Expect(httpCode).Should(Equal(http.StatusOK))
|
||||
resp, _ := DoGetRequest("/api/blocking/enable", sut.apiBlockingEnable)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
})
|
||||
|
||||
By("Query blocking status via API should return 'enabled'", func() {
|
||||
httpCode, body := DoGetRequest("/api/blocking/status", sut.apiBlockingStatus)
|
||||
Expect(httpCode).Should(Equal(http.StatusOK))
|
||||
resp, body := DoGetRequest("/api/blocking/status", sut.apiBlockingStatus)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
var result BlockingStatus
|
||||
err := json.NewDecoder(body).Decode(&result)
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -125,13 +128,15 @@ var _ = Describe("API tests", func() {
|
|||
})
|
||||
|
||||
By("disable blocking via API", func() {
|
||||
httpCode, _ := DoGetRequest("/api/blocking/disable?duration=500ms", sut.apiBlockingDisable)
|
||||
Expect(httpCode).Should(Equal(http.StatusOK))
|
||||
resp, _ := DoGetRequest("/api/blocking/disable?duration=500ms", sut.apiBlockingDisable)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
})
|
||||
|
||||
By("Query blocking status via API again should return 'disabled'", func() {
|
||||
httpCode, body := DoGetRequest("/api/blocking/status", sut.apiBlockingStatus)
|
||||
Expect(httpCode).Should(Equal(http.StatusOK))
|
||||
resp, body := DoGetRequest("/api/blocking/status", sut.apiBlockingStatus)
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
|
||||
var result BlockingStatus
|
||||
err := json.NewDecoder(body).Decode(&result)
|
||||
|
|
|
@ -120,8 +120,8 @@ var _ = Describe("Expiration cache", func() {
|
|||
val, ttl := cache.Get("key1")
|
||||
Expect(val).Should(Equal("val2"))
|
||||
Expect(ttl.Milliseconds()).Should(And(
|
||||
BeNumerically(">", 900)),
|
||||
BeNumerically("<=", 1000))
|
||||
BeNumerically(">", 900),
|
||||
BeNumerically("<=", 1000)))
|
||||
})
|
||||
|
||||
It("should delete the key if function returns nil", func() {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDNSPort = 53
|
||||
)
|
||||
|
||||
func NewHealthcheckCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "healthcheck",
|
||||
Short: "performs healthcheck",
|
||||
RunE: healthcheck,
|
||||
}
|
||||
|
||||
c.Flags().Uint16P("port", "p", defaultDNSPort, "healthcheck port 5333")
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func healthcheck(cmd *cobra.Command, args []string) error {
|
||||
port, _ := cmd.Flags().GetUint16("port")
|
||||
|
||||
c := new(dns.Client)
|
||||
c.Net = "tcp"
|
||||
m := new(dns.Msg)
|
||||
m.SetQuestion("healthcheck.blocky.", dns.TypeA)
|
||||
|
||||
_, _, err := c.Exchange(m, net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port)))
|
||||
|
||||
if err == nil {
|
||||
fmt.Println("OK")
|
||||
} else {
|
||||
fmt.Println("NOT OK")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Healthcheck command", func() {
|
||||
Describe("Call healthcheck command", func() {
|
||||
It("should fail", func() {
|
||||
c := NewHealthcheckCommand()
|
||||
c.SetArgs([]string{"-p", "5344"})
|
||||
|
||||
err := c.Execute()
|
||||
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
|
||||
It("shoul succeed", func() {
|
||||
srv := createMockServer()
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
err := srv.ListenAndServe()
|
||||
Expect(err).Should(Succeed())
|
||||
}()
|
||||
DeferCleanup(srv.Shutdown)
|
||||
|
||||
Eventually(func() error {
|
||||
c := NewHealthcheckCommand()
|
||||
c.SetArgs([]string{"-p", "5333"})
|
||||
|
||||
return c.Execute()
|
||||
}, "1s").Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func createMockServer() *dns.Server {
|
||||
res := &dns.Server{
|
||||
Addr: "127.0.0.1:5333",
|
||||
Net: "tcp",
|
||||
Handler: dns.NewServeMux(),
|
||||
NotifyStartedFunc: func() {
|
||||
fmt.Println("Mock helthcheck server is up")
|
||||
},
|
||||
}
|
||||
|
||||
th := res.Handler.(*dns.ServeMux)
|
||||
th.HandleFunc("healthcheck.blocky", func(w dns.ResponseWriter, request *dns.Msg) {
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(request)
|
||||
resp.Rcode = dns.RcodeSuccess
|
||||
|
||||
err := w.WriteMsg(resp)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
|
@ -2,7 +2,7 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/0xERR0R/blocky/api"
|
||||
|
@ -39,7 +39,7 @@ func refreshList(_ *cobra.Command, _ []string) error {
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
return fmt.Errorf("response NOK, %s %s", resp.Status, string(body))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/0xERR0R/blocky/api"
|
||||
|
@ -53,7 +53,7 @@ func query(cmd *cobra.Command, args []string) error {
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
return fmt.Errorf("response NOK, %s %s", resp.Status, string(body))
|
||||
}
|
||||
|
|
27
cmd/root.go
27
cmd/root.go
|
@ -2,7 +2,9 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
|
@ -20,9 +22,11 @@ var (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultPort = 4000
|
||||
defaultHost = "localhost"
|
||||
defaultConfigPath = "./config.yml"
|
||||
defaultPort = 4000
|
||||
defaultHost = "localhost"
|
||||
defaultConfigPath = "./config.yml"
|
||||
configFileEnvVar = "BLOCKY_CONFIG_FILE"
|
||||
configFileEnvVarOld = "CONFIG_FILE"
|
||||
)
|
||||
|
||||
// NewRootCommand creates a new root cli command instance
|
||||
|
@ -49,13 +53,14 @@ Complete documentation is available at https://github.com/0xERR0R/blocky`,
|
|||
NewVersionCommand(),
|
||||
newServeCommand(),
|
||||
newBlockingCommand(),
|
||||
NewListsCommand())
|
||||
NewListsCommand(),
|
||||
NewHealthcheckCommand())
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func apiURL(path string) string {
|
||||
return fmt.Sprintf("http://%s:%d%s", apiHost, apiPort, path)
|
||||
return fmt.Sprintf("http://%s%s", net.JoinHostPort(apiHost, strconv.Itoa(int(apiPort))), path)
|
||||
}
|
||||
|
||||
//nolint:gochecknoinits
|
||||
|
@ -64,6 +69,18 @@ func init() {
|
|||
}
|
||||
|
||||
func initConfig() {
|
||||
if configPath == defaultConfigPath {
|
||||
val, present := os.LookupEnv(configFileEnvVar)
|
||||
if present {
|
||||
configPath = val
|
||||
} else {
|
||||
val, present = os.LookupEnv(configFileEnvVarOld)
|
||||
if present {
|
||||
configPath = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := config.LoadConfig(configPath, false)
|
||||
if err != nil {
|
||||
util.FatalOnError("unable to load configuration: ", err)
|
||||
|
|
|
@ -1,23 +1,73 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
. "github.com/0xERR0R/blocky/helpertest"
|
||||
)
|
||||
|
||||
var _ = Describe("Version command", func() {
|
||||
var _ = Describe("root command", func() {
|
||||
When("Version command is called", func() {
|
||||
log.Log().ExitFunc = nil
|
||||
It("should execute without error", func() {
|
||||
c := NewRootCommand()
|
||||
c.SetOutput(ioutil.Discard)
|
||||
c.SetOutput(io.Discard)
|
||||
c.SetArgs([]string{"help"})
|
||||
err := c.Execute()
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
})
|
||||
When("Config provided", func() {
|
||||
var (
|
||||
tmpDir *TmpFolder
|
||||
tmpFile *TmpFile
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
configPath = defaultConfigPath
|
||||
|
||||
tmpDir = NewTmpFolder("RootCommand")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
|
||||
tmpFile = tmpDir.CreateStringFile("config",
|
||||
"upstream:",
|
||||
" default:",
|
||||
" - 1.1.1.1",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" ads:",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
" clientGroupsBlock:",
|
||||
" default:",
|
||||
" - ads",
|
||||
"port: 5333",
|
||||
)
|
||||
Expect(tmpFile.Error).Should(Succeed())
|
||||
})
|
||||
|
||||
It("should accept old env var", func() {
|
||||
os.Setenv(configFileEnvVarOld, tmpFile.Path)
|
||||
DeferCleanup(func() { os.Unsetenv(configFileEnvVarOld) })
|
||||
|
||||
initConfig()
|
||||
|
||||
Expect(configPath).Should(Equal(tmpFile.Path))
|
||||
})
|
||||
|
||||
It("should accept new env var", func() {
|
||||
os.Setenv(configFileEnvVar, tmpFile.Path)
|
||||
DeferCleanup(func() { os.Unsetenv(configFileEnvVar) })
|
||||
|
||||
initConfig()
|
||||
|
||||
Expect(configPath).Should(Equal(tmpFile.Path))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Serve command", func() {
|
||||
|
@ -21,13 +20,18 @@ var _ = Describe("Serve command", func() {
|
|||
|
||||
isConfigMandatory = false
|
||||
|
||||
grClosure := make(chan interface{})
|
||||
|
||||
go func() {
|
||||
_ = startServer(newServeCommand(), []string{})
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := startServer(newServeCommand(), []string{})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
|
||||
close(grClosure)
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
done <- true
|
||||
Eventually(grClosure).Should(BeClosed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -21,4 +21,5 @@ func printVersion(_ *cobra.Command, _ []string) {
|
|||
fmt.Println("blocky")
|
||||
fmt.Printf("Version: %s\n", util.Version)
|
||||
fmt.Printf("Build time: %s\n", util.BuildTime)
|
||||
fmt.Printf("Architecture: %s\n", util.Architecture)
|
||||
}
|
||||
|
|
13
codecov.yml
13
codecov.yml
|
@ -1,3 +1,16 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 80%
|
||||
patch:
|
||||
default:
|
||||
# basic
|
||||
target: auto
|
||||
threshold: 0%
|
||||
base: auto
|
||||
only_pulls: true
|
||||
ignore:
|
||||
- "resolver/mocks.go"
|
||||
- "**/*_enum.go"
|
||||
|
|
251
config/config.go
251
config/config.go
|
@ -1,10 +1,9 @@
|
|||
//go:generate go-enum -f=$GOFILE --marshal --names
|
||||
//go:generate go run github.com/abice/go-enum -f=$GOFILE --marshal --names
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -12,6 +11,7 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
@ -36,6 +36,39 @@ const (
|
|||
// )
|
||||
type NetProtocol uint16
|
||||
|
||||
// IPVersion represents IP protocol version(s). ENUM(
|
||||
// dual // IPv4 and IPv6
|
||||
// v4 // IPv4 only
|
||||
// v6 // IPv6 only
|
||||
// )
|
||||
type IPVersion uint8
|
||||
|
||||
func (ipv IPVersion) Net() string {
|
||||
switch ipv {
|
||||
case IPVersionDual:
|
||||
return "ip"
|
||||
case IPVersionV4:
|
||||
return "ip4"
|
||||
case IPVersionV6:
|
||||
return "ip6"
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("bad value: %s", ipv))
|
||||
}
|
||||
|
||||
func (ipv IPVersion) QTypes() []dns.Type {
|
||||
switch ipv {
|
||||
case IPVersionDual:
|
||||
return []dns.Type{dns.Type(dns.TypeA), dns.Type(dns.TypeAAAA)}
|
||||
case IPVersionV4:
|
||||
return []dns.Type{dns.Type(dns.TypeA)}
|
||||
case IPVersionV6:
|
||||
return []dns.Type{dns.Type(dns.TypeAAAA)}
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("bad value: %s", ipv))
|
||||
}
|
||||
|
||||
// QueryLogType type of the query log ENUM(
|
||||
// console // use logger as fallback
|
||||
// none // no logging
|
||||
|
@ -46,6 +79,13 @@ type NetProtocol uint16
|
|||
// )
|
||||
type QueryLogType int16
|
||||
|
||||
// StartStrategyType upstart strategy ENUM(
|
||||
// blocking // synchronously download blocking lists on startup
|
||||
// failOnError // synchronously download blocking lists on startup and shutdown on error
|
||||
// fast // asyncronously download blocking lists on startup
|
||||
// )
|
||||
type StartStrategyType uint16
|
||||
|
||||
type QType dns.Type
|
||||
|
||||
func (c QType) String() string {
|
||||
|
@ -93,10 +133,11 @@ var netDefaultPort = map[NetProtocol]uint16{
|
|||
|
||||
// Upstream is the definition of external DNS server
|
||||
type Upstream struct {
|
||||
Net NetProtocol
|
||||
Host string
|
||||
Port uint16
|
||||
Path string
|
||||
Net NetProtocol
|
||||
Host string
|
||||
Port uint16
|
||||
Path string
|
||||
CommonName string // Common Name to use for certificate verification; optional. "" uses .Host
|
||||
}
|
||||
|
||||
// IsDefault returns true if u is the default value
|
||||
|
@ -315,12 +356,14 @@ func (s *QTypeSet) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|||
var validDomain = regexp.MustCompile(
|
||||
`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`)
|
||||
|
||||
// ParseUpstream creates new Upstream from passed string in format [net]:host[:port][/path]
|
||||
// ParseUpstream creates new Upstream from passed string in format [net]:host[:port][/path][#commonname]
|
||||
func ParseUpstream(upstream string) (Upstream, error) {
|
||||
var path string
|
||||
|
||||
var port uint16
|
||||
|
||||
commonName, upstream := extractCommonName(upstream)
|
||||
|
||||
n, upstream := extractNet(upstream)
|
||||
|
||||
path, upstream = extractPath(upstream)
|
||||
|
@ -357,13 +400,20 @@ func ParseUpstream(upstream string) (Upstream, error) {
|
|||
}
|
||||
|
||||
return Upstream{
|
||||
Net: n,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: path,
|
||||
Net: n,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: path,
|
||||
CommonName: commonName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func extractCommonName(in string) (string, string) {
|
||||
upstream, cn, _ := strings.Cut(in, "#")
|
||||
|
||||
return cn, upstream
|
||||
}
|
||||
|
||||
func extractPath(in string) (path string, upstream string) {
|
||||
slashIdx := strings.Index(in, "/")
|
||||
|
||||
|
@ -399,33 +449,37 @@ func extractNet(upstream string) (NetProtocol, string) {
|
|||
// Config main configuration
|
||||
// nolint:maligned
|
||||
type Config struct {
|
||||
Upstream UpstreamConfig `yaml:"upstream"`
|
||||
UpstreamTimeout Duration `yaml:"upstreamTimeout" default:"2s"`
|
||||
CustomDNS CustomDNSConfig `yaml:"customDNS"`
|
||||
Conditional ConditionalUpstreamConfig `yaml:"conditional"`
|
||||
Blocking BlockingConfig `yaml:"blocking"`
|
||||
ClientLookup ClientLookupConfig `yaml:"clientLookup"`
|
||||
Caching CachingConfig `yaml:"caching"`
|
||||
QueryLog QueryLogConfig `yaml:"queryLog"`
|
||||
Prometheus PrometheusConfig `yaml:"prometheus"`
|
||||
Redis RedisConfig `yaml:"redis"`
|
||||
LogLevel log.Level `yaml:"logLevel" default:"info"`
|
||||
LogFormat log.FormatType `yaml:"logFormat" default:"text"`
|
||||
LogPrivacy bool `yaml:"logPrivacy" default:"false"`
|
||||
LogTimestamp bool `yaml:"logTimestamp" default:"true"`
|
||||
DNSPorts ListenConfig `yaml:"port" default:"[\"53\"]"`
|
||||
HTTPPorts ListenConfig `yaml:"httpPort"`
|
||||
HTTPSPorts ListenConfig `yaml:"httpsPort"`
|
||||
TLSPorts ListenConfig `yaml:"tlsPort"`
|
||||
DoHUserAgent string `yaml:"dohUserAgent"`
|
||||
MinTLSServeVer string `yaml:"minTlsServeVersion" default:"1.2"`
|
||||
Upstream UpstreamConfig `yaml:"upstream"`
|
||||
UpstreamTimeout Duration `yaml:"upstreamTimeout" default:"2s"`
|
||||
ConnectIPVersion IPVersion `yaml:"connectIPVersion"`
|
||||
CustomDNS CustomDNSConfig `yaml:"customDNS"`
|
||||
Conditional ConditionalUpstreamConfig `yaml:"conditional"`
|
||||
Blocking BlockingConfig `yaml:"blocking"`
|
||||
ClientLookup ClientLookupConfig `yaml:"clientLookup"`
|
||||
Caching CachingConfig `yaml:"caching"`
|
||||
QueryLog QueryLogConfig `yaml:"queryLog"`
|
||||
Prometheus PrometheusConfig `yaml:"prometheus"`
|
||||
Redis RedisConfig `yaml:"redis"`
|
||||
LogLevel log.Level `yaml:"logLevel" default:"info"`
|
||||
LogFormat log.FormatType `yaml:"logFormat" default:"text"`
|
||||
LogPrivacy bool `yaml:"logPrivacy" default:"false"`
|
||||
LogTimestamp bool `yaml:"logTimestamp" default:"true"`
|
||||
DNSPorts ListenConfig `yaml:"port" default:"[\"53\"]"`
|
||||
HTTPPorts ListenConfig `yaml:"httpPort"`
|
||||
HTTPSPorts ListenConfig `yaml:"httpsPort"`
|
||||
TLSPorts ListenConfig `yaml:"tlsPort"`
|
||||
DoHUserAgent string `yaml:"dohUserAgent"`
|
||||
MinTLSServeVer string `yaml:"minTlsServeVersion" default:"1.2"`
|
||||
StartVerifyUpstream bool `yaml:"startVerifyUpstream" default:"false"`
|
||||
// Deprecated
|
||||
DisableIPv6 bool `yaml:"disableIPv6" default:"false"`
|
||||
CertFile string `yaml:"certFile"`
|
||||
KeyFile string `yaml:"keyFile"`
|
||||
BootstrapDNS BootstrapConfig `yaml:"bootstrapDns"`
|
||||
HostsFile HostsFileConfig `yaml:"hostsFile"`
|
||||
FqdnOnly bool `yaml:"fqdnOnly" default:"false"`
|
||||
Filtering FilteringConfig `yaml:"filtering"`
|
||||
Ede EdeConfig `yaml:"ede"`
|
||||
}
|
||||
|
||||
type BootstrapConfig bootstrapConfig // to avoid infinite recursion. See BootstrapConfig.UnmarshalYAML.
|
||||
|
@ -447,7 +501,8 @@ type UpstreamConfig struct {
|
|||
|
||||
// RewriteConfig custom DNS configuration
|
||||
type RewriteConfig struct {
|
||||
Rewrite map[string]string `yaml:"rewrite"`
|
||||
Rewrite map[string]string `yaml:"rewrite"`
|
||||
FallbackUpstream bool `yaml:"fallbackUpstream" default:"false"`
|
||||
}
|
||||
|
||||
// CustomDNSConfig custom DNS configuration
|
||||
|
@ -476,17 +531,19 @@ type ConditionalUpstreamMapping struct {
|
|||
|
||||
// BlockingConfig configuration for query blocking
|
||||
type BlockingConfig struct {
|
||||
BlackLists map[string][]string `yaml:"blackLists"`
|
||||
WhiteLists map[string][]string `yaml:"whiteLists"`
|
||||
ClientGroupsBlock map[string][]string `yaml:"clientGroupsBlock"`
|
||||
BlockType string `yaml:"blockType" default:"ZEROIP"`
|
||||
BlockTTL Duration `yaml:"blockTTL" default:"6h"`
|
||||
DownloadTimeout Duration `yaml:"downloadTimeout" default:"60s"`
|
||||
DownloadAttempts uint `yaml:"downloadAttempts" default:"3"`
|
||||
DownloadCooldown Duration `yaml:"downloadCooldown" default:"1s"`
|
||||
RefreshPeriod Duration `yaml:"refreshPeriod" default:"4h"`
|
||||
FailStartOnListError bool `yaml:"failStartOnListError" default:"false"`
|
||||
ProcessingConcurrency uint `yaml:"processingConcurrency" default:"4"`
|
||||
BlackLists map[string][]string `yaml:"blackLists"`
|
||||
WhiteLists map[string][]string `yaml:"whiteLists"`
|
||||
ClientGroupsBlock map[string][]string `yaml:"clientGroupsBlock"`
|
||||
BlockType string `yaml:"blockType" default:"ZEROIP"`
|
||||
BlockTTL Duration `yaml:"blockTTL" default:"6h"`
|
||||
DownloadTimeout Duration `yaml:"downloadTimeout" default:"60s"`
|
||||
DownloadAttempts uint `yaml:"downloadAttempts" default:"3"`
|
||||
DownloadCooldown Duration `yaml:"downloadCooldown" default:"1s"`
|
||||
RefreshPeriod Duration `yaml:"refreshPeriod" default:"4h"`
|
||||
// Deprecated
|
||||
FailStartOnListError bool `yaml:"failStartOnListError" default:"false"`
|
||||
ProcessingConcurrency uint `yaml:"processingConcurrency" default:"4"`
|
||||
StartStrategy StartStrategyType `yaml:"startStrategy" default:"blocking"`
|
||||
}
|
||||
|
||||
// ClientLookupConfig configuration for the client lookup
|
||||
|
@ -528,20 +585,31 @@ type RedisConfig struct {
|
|||
}
|
||||
|
||||
type HostsFileConfig struct {
|
||||
Filepath string `yaml:"filePath"`
|
||||
HostsTTL Duration `yaml:"hostsTTL" default:"1h"`
|
||||
RefreshPeriod Duration `yaml:"refreshPeriod" default:"1h"`
|
||||
Filepath string `yaml:"filePath"`
|
||||
HostsTTL Duration `yaml:"hostsTTL" default:"1h"`
|
||||
RefreshPeriod Duration `yaml:"refreshPeriod" default:"1h"`
|
||||
FilterLoopback bool `yaml:"filterLoopback"`
|
||||
}
|
||||
|
||||
type FilteringConfig struct {
|
||||
QueryTypes QTypeSet `yaml:"queryTypes"`
|
||||
}
|
||||
|
||||
type EdeConfig struct {
|
||||
Enable bool `yaml:"enable" default:"false"`
|
||||
}
|
||||
|
||||
// nolint:gochecknoglobals
|
||||
var config = &Config{}
|
||||
var (
|
||||
config = &Config{}
|
||||
cfgLock sync.RWMutex
|
||||
)
|
||||
|
||||
// LoadConfig creates new config from YAML file or a directory containing YAML files
|
||||
func LoadConfig(path string, mandatory bool) (*Config, error) {
|
||||
cfgLock.Lock()
|
||||
defer cfgLock.Unlock()
|
||||
|
||||
cfg := Config{}
|
||||
if err := defaults.Set(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("can't apply default values: %w", err)
|
||||
|
@ -562,32 +630,14 @@ func LoadConfig(path string, mandatory bool) (*Config, error) {
|
|||
|
||||
var data []byte
|
||||
|
||||
if fs.IsDir() { //nolint:nestif
|
||||
err = filepath.WalkDir(path, func(filePath string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == filePath {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileData, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = append(data, []byte("\n")...)
|
||||
data = append(data, fileData...)
|
||||
|
||||
return nil
|
||||
})
|
||||
if fs.IsDir() {
|
||||
data, err = readFromDir(path, data)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read config files: %w", err)
|
||||
}
|
||||
} else {
|
||||
data, err = ioutil.ReadFile(path)
|
||||
data, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read config file: %w", err)
|
||||
}
|
||||
|
@ -603,6 +653,57 @@ func LoadConfig(path string, mandatory bool) (*Config, error) {
|
|||
return &cfg, nil
|
||||
}
|
||||
|
||||
func readFromDir(path string, data []byte) ([]byte, error) {
|
||||
err := filepath.WalkDir(path, func(filePath string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if path == filePath {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore non YAML files
|
||||
if !strings.HasSuffix(filePath, ".yml") && !strings.HasSuffix(filePath, ".yaml") {
|
||||
return nil
|
||||
}
|
||||
|
||||
isRegular, err := isRegularFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ignore non regular files (directories, sockets, etc.)
|
||||
if !isRegular {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileData, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = append(data, []byte("\n")...)
|
||||
data = append(data, fileData...)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
// isRegularFile follows symlinks, so the result is `true` for a symlink to a regular file.
|
||||
func isRegularFile(path string) (bool, error) {
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
isRegular := stat.Mode()&os.ModeType == 0
|
||||
|
||||
return isRegular, nil
|
||||
}
|
||||
|
||||
func unmarshalConfig(data []byte, cfg *Config) error {
|
||||
err := yaml.UnmarshalStrict(data, cfg)
|
||||
if err != nil {
|
||||
|
@ -620,10 +721,24 @@ func validateConfig(cfg *Config) {
|
|||
|
||||
cfg.Filtering.QueryTypes.Insert(dns.Type(dns.TypeAAAA))
|
||||
}
|
||||
|
||||
if cfg.Blocking.FailStartOnListError {
|
||||
log.Log().Warnf("'blocking.failStartOnListError' is deprecated. Please use 'blocking.startStrategy'" +
|
||||
" with 'failOnError' instead.")
|
||||
|
||||
if cfg.Blocking.StartStrategy == StartStrategyTypeBlocking {
|
||||
cfg.Blocking.StartStrategy = StartStrategyTypeFailOnError
|
||||
} else if cfg.Blocking.StartStrategy == StartStrategyTypeFast {
|
||||
log.Log().Warnf("'blocking.startStrategy' with 'fast' will ignore 'blocking.failStartOnListError'.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfig returns the current config
|
||||
func GetConfig() *Config {
|
||||
cfgLock.RLock()
|
||||
defer cfgLock.RUnlock()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,79 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// IPVersionDual is a IPVersion of type Dual.
|
||||
// IPv4 and IPv6
|
||||
IPVersionDual IPVersion = iota
|
||||
// IPVersionV4 is a IPVersion of type V4.
|
||||
// IPv4 only
|
||||
IPVersionV4
|
||||
// IPVersionV6 is a IPVersion of type V6.
|
||||
// IPv6 only
|
||||
IPVersionV6
|
||||
)
|
||||
|
||||
var ErrInvalidIPVersion = fmt.Errorf("not a valid IPVersion, try [%s]", strings.Join(_IPVersionNames, ", "))
|
||||
|
||||
const _IPVersionName = "dualv4v6"
|
||||
|
||||
var _IPVersionNames = []string{
|
||||
_IPVersionName[0:4],
|
||||
_IPVersionName[4:6],
|
||||
_IPVersionName[6:8],
|
||||
}
|
||||
|
||||
// IPVersionNames returns a list of possible string values of IPVersion.
|
||||
func IPVersionNames() []string {
|
||||
tmp := make([]string, len(_IPVersionNames))
|
||||
copy(tmp, _IPVersionNames)
|
||||
return tmp
|
||||
}
|
||||
|
||||
var _IPVersionMap = map[IPVersion]string{
|
||||
IPVersionDual: _IPVersionName[0:4],
|
||||
IPVersionV4: _IPVersionName[4:6],
|
||||
IPVersionV6: _IPVersionName[6:8],
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (x IPVersion) String() string {
|
||||
if str, ok := _IPVersionMap[x]; ok {
|
||||
return str
|
||||
}
|
||||
return fmt.Sprintf("IPVersion(%d)", x)
|
||||
}
|
||||
|
||||
var _IPVersionValue = map[string]IPVersion{
|
||||
_IPVersionName[0:4]: IPVersionDual,
|
||||
_IPVersionName[4:6]: IPVersionV4,
|
||||
_IPVersionName[6:8]: IPVersionV6,
|
||||
}
|
||||
|
||||
// ParseIPVersion attempts to convert a string to a IPVersion.
|
||||
func ParseIPVersion(name string) (IPVersion, error) {
|
||||
if x, ok := _IPVersionValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return IPVersion(0), fmt.Errorf("%s is %w", name, ErrInvalidIPVersion)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
func (x IPVersion) MarshalText() ([]byte, error) {
|
||||
return []byte(x.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the text unmarshaller method.
|
||||
func (x *IPVersion) UnmarshalText(text []byte) error {
|
||||
name := string(text)
|
||||
tmp, err := ParseIPVersion(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = tmp
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// NetProtocolTcpUdp is a NetProtocol of type Tcp+Udp.
|
||||
// TCP and UDP protocols
|
||||
|
@ -23,6 +96,8 @@ const (
|
|||
NetProtocolHttps
|
||||
)
|
||||
|
||||
var ErrInvalidNetProtocol = fmt.Errorf("not a valid NetProtocol, try [%s]", strings.Join(_NetProtocolNames, ", "))
|
||||
|
||||
const _NetProtocolName = "tcp+udptcp-tlshttps"
|
||||
|
||||
var _NetProtocolNames = []string{
|
||||
|
@ -63,7 +138,7 @@ func ParseNetProtocol(name string) (NetProtocol, error) {
|
|||
if x, ok := _NetProtocolValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return NetProtocol(0), fmt.Errorf("%s is not a valid NetProtocol, try [%s]", name, strings.Join(_NetProtocolNames, ", "))
|
||||
return NetProtocol(0), fmt.Errorf("%s is %w", name, ErrInvalidNetProtocol)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -103,6 +178,8 @@ const (
|
|||
QueryLogTypeCsvClient
|
||||
)
|
||||
|
||||
var ErrInvalidQueryLogType = fmt.Errorf("not a valid QueryLogType, try [%s]", strings.Join(_QueryLogTypeNames, ", "))
|
||||
|
||||
const _QueryLogTypeName = "consolenonemysqlpostgresqlcsvcsv-client"
|
||||
|
||||
var _QueryLogTypeNames = []string{
|
||||
|
@ -152,7 +229,7 @@ func ParseQueryLogType(name string) (QueryLogType, error) {
|
|||
if x, ok := _QueryLogTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return QueryLogType(0), fmt.Errorf("%s is not a valid QueryLogType, try [%s]", name, strings.Join(_QueryLogTypeNames, ", "))
|
||||
return QueryLogType(0), fmt.Errorf("%s is %w", name, ErrInvalidQueryLogType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -170,3 +247,76 @@ func (x *QueryLogType) UnmarshalText(text []byte) error {
|
|||
*x = tmp
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// StartStrategyTypeBlocking is a StartStrategyType of type Blocking.
|
||||
// synchronously download blocking lists on startup
|
||||
StartStrategyTypeBlocking StartStrategyType = iota
|
||||
// StartStrategyTypeFailOnError is a StartStrategyType of type FailOnError.
|
||||
// synchronously download blocking lists on startup and shutdown on error
|
||||
StartStrategyTypeFailOnError
|
||||
// StartStrategyTypeFast is a StartStrategyType of type Fast.
|
||||
// asyncronously download blocking lists on startup
|
||||
StartStrategyTypeFast
|
||||
)
|
||||
|
||||
var ErrInvalidStartStrategyType = fmt.Errorf("not a valid StartStrategyType, try [%s]", strings.Join(_StartStrategyTypeNames, ", "))
|
||||
|
||||
const _StartStrategyTypeName = "blockingfailOnErrorfast"
|
||||
|
||||
var _StartStrategyTypeNames = []string{
|
||||
_StartStrategyTypeName[0:8],
|
||||
_StartStrategyTypeName[8:19],
|
||||
_StartStrategyTypeName[19:23],
|
||||
}
|
||||
|
||||
// StartStrategyTypeNames returns a list of possible string values of StartStrategyType.
|
||||
func StartStrategyTypeNames() []string {
|
||||
tmp := make([]string, len(_StartStrategyTypeNames))
|
||||
copy(tmp, _StartStrategyTypeNames)
|
||||
return tmp
|
||||
}
|
||||
|
||||
var _StartStrategyTypeMap = map[StartStrategyType]string{
|
||||
StartStrategyTypeBlocking: _StartStrategyTypeName[0:8],
|
||||
StartStrategyTypeFailOnError: _StartStrategyTypeName[8:19],
|
||||
StartStrategyTypeFast: _StartStrategyTypeName[19:23],
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (x StartStrategyType) String() string {
|
||||
if str, ok := _StartStrategyTypeMap[x]; ok {
|
||||
return str
|
||||
}
|
||||
return fmt.Sprintf("StartStrategyType(%d)", x)
|
||||
}
|
||||
|
||||
var _StartStrategyTypeValue = map[string]StartStrategyType{
|
||||
_StartStrategyTypeName[0:8]: StartStrategyTypeBlocking,
|
||||
_StartStrategyTypeName[8:19]: StartStrategyTypeFailOnError,
|
||||
_StartStrategyTypeName[19:23]: StartStrategyTypeFast,
|
||||
}
|
||||
|
||||
// ParseStartStrategyType attempts to convert a string to a StartStrategyType.
|
||||
func ParseStartStrategyType(name string) (StartStrategyType, error) {
|
||||
if x, ok := _StartStrategyTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return StartStrategyType(0), fmt.Errorf("%s is %w", name, ErrInvalidStartStrategyType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
func (x StartStrategyType) MarshalText() ([]byte, error) {
|
||||
return []byte(x.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the text unmarshaller method.
|
||||
func (x *StartStrategyType) UnmarshalText(text []byte) error {
|
||||
name := string(text)
|
||||
tmp, err := ParseStartStrategyType(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = tmp
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,26 +2,36 @@ package config
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/0xERR0R/blocky/helpertest"
|
||||
. "github.com/0xERR0R/blocky/log"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Config", func() {
|
||||
var (
|
||||
tmpDir *helpertest.TmpFolder
|
||||
err error
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
tmpDir = helpertest.NewTmpFolder("config")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
})
|
||||
|
||||
Describe("Creation of Config", func() {
|
||||
When("Test config file will be parsed", func() {
|
||||
It("should return a valid config struct", func() {
|
||||
err := os.Chdir("../testdata")
|
||||
Expect(err).Should(Succeed())
|
||||
confFile := writeConfigYml(tmpDir)
|
||||
Expect(confFile.Error).Should(Succeed())
|
||||
|
||||
_, err = LoadConfig("config.yml", true)
|
||||
_, err = LoadConfig(confFile.Path, true)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
defaultTestFileConfig()
|
||||
|
@ -29,36 +39,54 @@ var _ = Describe("Config", func() {
|
|||
})
|
||||
When("Test file does not exist", func() {
|
||||
It("should fail", func() {
|
||||
_, err := LoadConfig("../testdata/config-does-not-exist.yaml", true)
|
||||
_, err := LoadConfig(tmpDir.JoinPath("config-does-not-exist.yaml"), true)
|
||||
Expect(err).Should(Not(Succeed()))
|
||||
})
|
||||
})
|
||||
When("Multiple config files are used", func() {
|
||||
It("should return a valid config struct", func() {
|
||||
_, err := LoadConfig("../testdata/config/", true)
|
||||
err = writeConfigDir(tmpDir)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err := LoadConfig(tmpDir.Path, true)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
defaultTestFileConfig()
|
||||
})
|
||||
|
||||
It("should ignore non YAML files", func() {
|
||||
err = writeConfigDir(tmpDir)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
tmpDir.CreateStringFile("ignore-me.txt", "THIS SHOULD BE IGNORED!")
|
||||
|
||||
_, err := LoadConfig(tmpDir.Path, true)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
|
||||
It("should ignore non regular files", func() {
|
||||
err = writeConfigDir(tmpDir)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
tmpDir.CreateSubFolder("subfolder")
|
||||
tmpDir.CreateSubFolder("subfolder.yml")
|
||||
|
||||
_, err := LoadConfig(tmpDir.Path, true)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
})
|
||||
When("Config folder does not exist", func() {
|
||||
It("should fail", func() {
|
||||
_, err := LoadConfig("../testdata/does-not-exist-config/", true)
|
||||
_, err := LoadConfig(tmpDir.JoinPath("does-not-exist-config/"), true)
|
||||
Expect(err).Should(Not(Succeed()))
|
||||
})
|
||||
})
|
||||
When("config file is malformed", func() {
|
||||
It("should return error", func() {
|
||||
cfgFile := tmpDir.CreateStringFile("config.yml", "malformed_config")
|
||||
Expect(cfgFile.Error).Should(Succeed())
|
||||
|
||||
dir, err := ioutil.TempDir("", "blocky")
|
||||
defer os.Remove(dir)
|
||||
Expect(err).Should(Succeed())
|
||||
err = os.Chdir(dir)
|
||||
Expect(err).Should(Succeed())
|
||||
err = ioutil.WriteFile("config.yml", []byte("malformed_config"), 0600)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = LoadConfig("config.yml", true)
|
||||
_, err = LoadConfig(cfgFile.Path, true)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("wrong file structure"))
|
||||
})
|
||||
|
@ -172,22 +200,39 @@ bootstrapDns:
|
|||
})
|
||||
})
|
||||
|
||||
When("Deprecated parameter 'failStartOnListError' is set", func() {
|
||||
var (
|
||||
c Config
|
||||
)
|
||||
BeforeEach(func() {
|
||||
c = Config{
|
||||
Blocking: BlockingConfig{
|
||||
FailStartOnListError: true,
|
||||
StartStrategy: StartStrategyTypeBlocking,
|
||||
},
|
||||
}
|
||||
})
|
||||
It("should change StartStrategy blocking to failOnError", func() {
|
||||
validateConfig(&c)
|
||||
Expect(c.Blocking.StartStrategy).Should(Equal(StartStrategyTypeFailOnError))
|
||||
})
|
||||
It("shouldn't change StartStrategy if set to fast", func() {
|
||||
c.Blocking.StartStrategy = StartStrategyTypeFast
|
||||
validateConfig(&c)
|
||||
Expect(c.Blocking.StartStrategy).Should(Equal(StartStrategyTypeFast))
|
||||
})
|
||||
})
|
||||
|
||||
When("config directory does not exist", func() {
|
||||
It("should return error", func() {
|
||||
err := os.Chdir("../..")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = LoadConfig("config.yml", true)
|
||||
_, err = LoadConfig(tmpDir.JoinPath("config.yml"), true)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("no such file or directory"))
|
||||
|
||||
})
|
||||
|
||||
It("should use default config if config is not mandatory", func() {
|
||||
err := os.Chdir("../..")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = LoadConfig("config.yml", false)
|
||||
_, err = LoadConfig(tmpDir.JoinPath("config.yml"), false)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(config.LogLevel).Should(Equal(LevelInfo))
|
||||
|
@ -381,6 +426,10 @@ bootstrapDns:
|
|||
"tcp-tls:4.4.4.4",
|
||||
Upstream{Net: NetProtocolTcpTls, Host: "4.4.4.4", Port: 853},
|
||||
false),
|
||||
Entry("tcp-tls with common name",
|
||||
"tcp-tls:1.1.1.2#security.cloudflare-dns.com",
|
||||
Upstream{Net: NetProtocolTcpTls, Host: "1.1.1.2", Port: 853, CommonName: "security.cloudflare-dns.com"},
|
||||
false),
|
||||
Entry("DoH without port, use default",
|
||||
"https:4.4.4.4",
|
||||
Upstream{Net: NetProtocolHttps, Host: "4.4.4.4", Port: 443},
|
||||
|
@ -578,6 +627,126 @@ func defaultTestFileConfig() {
|
|||
|
||||
Expect(config.DoHUserAgent).Should(Equal("testBlocky"))
|
||||
Expect(config.MinTLSServeVer).Should(Equal("1.3"))
|
||||
Expect(config.StartVerifyUpstream).Should(BeFalse())
|
||||
|
||||
Expect(GetConfig()).Should(Not(BeNil()))
|
||||
}
|
||||
|
||||
func writeConfigYml(tmpDir *helpertest.TmpFolder) *helpertest.TmpFile {
|
||||
return tmpDir.CreateStringFile("config.yml",
|
||||
"upstream:",
|
||||
" default:",
|
||||
" - tcp+udp:8.8.8.8",
|
||||
" - tcp+udp:8.8.4.4",
|
||||
" - 1.1.1.1",
|
||||
"customDNS:",
|
||||
" mapping:",
|
||||
" my.duckdns.org: 192.168.178.3",
|
||||
" multiple.ips: 192.168.178.3,192.168.178.4,2001:0db8:85a3:08d3:1319:8a2e:0370:7344",
|
||||
"conditional:",
|
||||
" mapping:",
|
||||
" fritz.box: tcp+udp:192.168.178.1",
|
||||
" multiple.resolvers: tcp+udp:192.168.178.1,tcp+udp:192.168.178.2",
|
||||
"filtering:",
|
||||
" queryTypes:",
|
||||
" - AAAA",
|
||||
" - A",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" ads:",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
" - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
" - https://mirror1.malwaredomains.com/files/justdomains",
|
||||
" - http://sysctl.org/cameleon/hosts",
|
||||
" - https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
" special:",
|
||||
" - https://hosts-file.net/ad_servers.txt",
|
||||
" whiteLists:",
|
||||
" ads:",
|
||||
" - whitelist.txt",
|
||||
" clientGroupsBlock:",
|
||||
" default:",
|
||||
" - ads",
|
||||
" - special",
|
||||
" Laptop-D.fritz.box:",
|
||||
" - ads",
|
||||
" blockTTL: 1m",
|
||||
" refreshPeriod: 120",
|
||||
"clientLookup:",
|
||||
" upstream: 192.168.178.1",
|
||||
" singleNameOrder:",
|
||||
" - 2",
|
||||
" - 1",
|
||||
"queryLog:",
|
||||
" type: csv-client",
|
||||
" target: /opt/log",
|
||||
"port: 55553,:55554,[::1]:55555",
|
||||
"logLevel: debug",
|
||||
"dohUserAgent: testBlocky",
|
||||
"minTlsServeVersion: 1.3",
|
||||
"startVerifyUpstream: false")
|
||||
}
|
||||
|
||||
func writeConfigDir(tmpDir *helpertest.TmpFolder) error {
|
||||
f1 := tmpDir.CreateStringFile("config1.yaml",
|
||||
"upstream:",
|
||||
" default:",
|
||||
" - tcp+udp:8.8.8.8",
|
||||
" - tcp+udp:8.8.4.4",
|
||||
" - 1.1.1.1",
|
||||
"customDNS:",
|
||||
" mapping:",
|
||||
" my.duckdns.org: 192.168.178.3",
|
||||
" multiple.ips: 192.168.178.3,192.168.178.4,2001:0db8:85a3:08d3:1319:8a2e:0370:7344",
|
||||
"conditional:",
|
||||
" mapping:",
|
||||
" fritz.box: tcp+udp:192.168.178.1",
|
||||
" multiple.resolvers: tcp+udp:192.168.178.1,tcp+udp:192.168.178.2",
|
||||
"filtering:",
|
||||
" queryTypes:",
|
||||
" - AAAA",
|
||||
" - A")
|
||||
if f1.Error != nil {
|
||||
return f1.Error
|
||||
}
|
||||
|
||||
f2 := tmpDir.CreateStringFile("config2.yaml",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" ads:",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
" - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
" - https://mirror1.malwaredomains.com/files/justdomains",
|
||||
" - http://sysctl.org/cameleon/hosts",
|
||||
" - https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
" special:",
|
||||
" - https://hosts-file.net/ad_servers.txt",
|
||||
" whiteLists:",
|
||||
" ads:",
|
||||
" - whitelist.txt",
|
||||
" clientGroupsBlock:",
|
||||
" default:",
|
||||
" - ads",
|
||||
" - special",
|
||||
" Laptop-D.fritz.box:",
|
||||
" - ads",
|
||||
" blockTTL: 1m",
|
||||
" refreshPeriod: 120",
|
||||
"clientLookup:",
|
||||
" upstream: 192.168.178.1",
|
||||
" singleNameOrder:",
|
||||
" - 2",
|
||||
" - 1",
|
||||
"queryLog:",
|
||||
" type: csv-client",
|
||||
" target: /opt/log",
|
||||
"port: 55553,:55554,[::1]:55555",
|
||||
"logLevel: debug",
|
||||
"dohUserAgent: testBlocky",
|
||||
"minTlsServeVersion: 1.3",
|
||||
"startVerifyUpstream: false")
|
||||
|
||||
return f2.Error
|
||||
}
|
||||
|
|
|
@ -0,0 +1,882 @@
|
|||
{
|
||||
"__inputs": [
|
||||
{
|
||||
"name": "DS_POSTGRES",
|
||||
"label": "Postgres",
|
||||
"description": "",
|
||||
"type": "datasource",
|
||||
"pluginId": "postgres",
|
||||
"pluginName": "Postgres"
|
||||
}
|
||||
],
|
||||
"__requires": [
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "barchart",
|
||||
"name": "Bar chart",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "bargauge",
|
||||
"name": "Bar gauge",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "8.1.2"
|
||||
},
|
||||
{
|
||||
"type": "datasource",
|
||||
"id": "postgres",
|
||||
"name": "Postgres",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "piechart",
|
||||
"name": "Pie chart",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "table",
|
||||
"name": "Table",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "timeseries",
|
||||
"name": "Time series",
|
||||
"version": ""
|
||||
}
|
||||
],
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"target": {
|
||||
"limit": 100,
|
||||
"matchAny": false,
|
||||
"tags": [],
|
||||
"type": "dashboard"
|
||||
},
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"iteration": 1631130053746,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
}
|
||||
},
|
||||
"displayName": "${__field.labels.response_type}",
|
||||
"mappings": [],
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 14,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"options": {
|
||||
"displayLabels": [],
|
||||
"legend": {
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"values": [
|
||||
"value"
|
||||
]
|
||||
},
|
||||
"pieType": "pie",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"sum"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "8.1.2",
|
||||
"repeatDirection": "v",
|
||||
"targets": [
|
||||
{
|
||||
"format": "time_series",
|
||||
"group": [],
|
||||
"metricColumn": "none",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT t.response_type, max(t.request_Ts) as time, count(*) as cnt from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\n group by t.response_type\n order by time",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"value"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"timeColumn": "time",
|
||||
"where": [
|
||||
{
|
||||
"name": "$__timeFilter",
|
||||
"params": [],
|
||||
"type": "macro"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Query count by response type",
|
||||
"transformations": [],
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
}
|
||||
},
|
||||
"mappings": []
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
},
|
||||
"id": 16,
|
||||
"options": {
|
||||
"displayLabels": [],
|
||||
"legend": {
|
||||
"displayMode": "table",
|
||||
"placement": "right",
|
||||
"values": [
|
||||
"value"
|
||||
]
|
||||
},
|
||||
"pieType": "pie",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"format": "time_series",
|
||||
"group": [],
|
||||
"metricColumn": "none",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT max(t.request_ts) AS time,\n case when t.reason like 'BLOCKED%' then SPLIT_PART(SPLIT_PART(t.reason,'(',-1), ')',1) else '' end AS metric,\n count(t.reason) AS cnt\nFROM log_entries t\nWHERE t.response_type ='BLOCKED'\n AND $__timeFilter(t.request_Ts)\n AND t.client_name in ($client_name)\n AND (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\nGROUP BY 2\nORDER BY time",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"duration_ms"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"table": "log_entries",
|
||||
"timeColumn": "request_ts",
|
||||
"timeColumnType": "timestamp",
|
||||
"where": [
|
||||
{
|
||||
"name": "$__timeFilter",
|
||||
"params": [],
|
||||
"type": "macro"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "Blocked by Blacklist",
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 13,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"options": {
|
||||
"displayMode": "gradient",
|
||||
"orientation": "horizontal",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": true
|
||||
},
|
||||
"showUnfilled": true,
|
||||
"text": {}
|
||||
},
|
||||
"pluginVersion": "8.1.2",
|
||||
"repeatDirection": "v",
|
||||
"targets": [
|
||||
{
|
||||
"format": "table",
|
||||
"group": [],
|
||||
"metricColumn": "none",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT max(t.request_Ts) as time, t.client_name as metric, count(*) as cnt from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\n group by t.client_name\n order by 3 desc",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"value"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"timeColumn": "time",
|
||||
"where": [
|
||||
{
|
||||
"name": "$__timeFilter",
|
||||
"params": [],
|
||||
"type": "macro"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Query count by client",
|
||||
"transformations": [],
|
||||
"type": "bargauge"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"description": "Top 20 effective top level domain plus one more label",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"fillOpacity": 67,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineWidth": 2
|
||||
},
|
||||
"displayName": "count",
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8
|
||||
},
|
||||
"id": 11,
|
||||
"options": {
|
||||
"barWidth": 0.26,
|
||||
"groupWidth": 0.7,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "hidden",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "never",
|
||||
"stacking": "none",
|
||||
"text": {
|
||||
"valueSize": 10
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"format": "table",
|
||||
"group": [],
|
||||
"hide": false,
|
||||
"metricColumn": "question_name",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT t.effective_tldp as metric, count(*) as value from log_entries t \nWHERE $__timeFilter(t.request_Ts) \n and t.response_type in ($response_type) \n and t.client_name in ($client_name) \n and (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0) \n group by t.effective_tldp order by count(*) desc limit 20",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"value"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"table": "log_entries",
|
||||
"timeColumn": "request_ts",
|
||||
"where": []
|
||||
}
|
||||
],
|
||||
"title": "Top 20 effective TLD+1",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"fillOpacity": 67,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineWidth": 2
|
||||
},
|
||||
"displayName": "count",
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"barWidth": 0.26,
|
||||
"groupWidth": 0.7,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "hidden",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "never",
|
||||
"stacking": "none",
|
||||
"text": {
|
||||
"valueSize": 10
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"format": "table",
|
||||
"group": [],
|
||||
"hide": false,
|
||||
"metricColumn": "question_name",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT t.question_name as metric, count(*) as value from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0) \n group by t.question_name order by count(*) desc limit 20",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"value"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"table": "log_entries",
|
||||
"timeColumn": "request_ts",
|
||||
"where": []
|
||||
}
|
||||
],
|
||||
"title": "Top 20 queried domains",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "queries count",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "bars",
|
||||
"fillOpacity": 35,
|
||||
"gradientMode": "hue",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineStyle": {
|
||||
"fill": "solid"
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"pointSize": 12,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": 3600000,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"displayName": "${__field.labels.client_name}",
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "right"
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "8.1.2",
|
||||
"targets": [
|
||||
{
|
||||
"format": "time_series",
|
||||
"group": [],
|
||||
"metricColumn": "none",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT\n $__timeGroupAlias(t.request_Ts, '30m'),\n t.client_name,\n count(*) as c\nFROM log_entries t\nWHERE\n $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\nGROUP BY 1,2\nORDER BY 1",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"duration_ms"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"table": "log_entries",
|
||||
"timeColumn": "request_ts",
|
||||
"timeColumnType": "timestamp",
|
||||
"where": [
|
||||
{
|
||||
"name": "$__timeFilter",
|
||||
"params": [],
|
||||
"type": "macro"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "Queries number per client (30m)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": -1,
|
||||
"drawStyle": "bars",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "stepBefore",
|
||||
"lineStyle": {
|
||||
"fill": "solid"
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "line"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "dtdurationms"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 23
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "hidden",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"format": "time_series",
|
||||
"group": [],
|
||||
"metricColumn": "none",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT\n EXTRACT(EPOCH from t.request_Ts) as time,\n t.duration_ms\nFROM log_entries t\nWHERE\n $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0)\nORDER BY request_ts",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"duration_ms"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"table": "log_entries",
|
||||
"timeColumn": "request_ts",
|
||||
"timeColumnType": "timestamp",
|
||||
"where": [
|
||||
{
|
||||
"name": "$__timeFilter",
|
||||
"params": [],
|
||||
"type": "macro"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "Query duration",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"description": "Last 100 queries, newest on top",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": null,
|
||||
"displayMode": "auto",
|
||||
"filterable": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "time"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "dateTimeAsIsoNoDateIfToday"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 31
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"showHeader": true
|
||||
},
|
||||
"pluginVersion": "8.1.2",
|
||||
"targets": [
|
||||
{
|
||||
"format": "table",
|
||||
"group": [],
|
||||
"metricColumn": "none",
|
||||
"rawQuery": true,
|
||||
"rawSql": "SELECT EXTRACT(EPOCH from t.request_Ts) as \"time\", \n t.client_ip as \"client IP\", \n t.client_name as \"client name\", \n t.duration_ms as \"duration in ms\", \n t.response_type as \"response type\", \n t.question_type as \"question type\", \n t.question_name as \"question name\", \n t.effective_tldp as \"effective TLD+1\", \n t.answer as \"answer\" from log_entries t \n WHERE $__timeFilter(t.request_Ts) and \n t.response_type in ($response_type) and \n t.client_name in ($client_name) and \n (length('$question') = 0 or POSITION(lower('$question') IN t.question_name) > 0) \n order by t.request_Ts desc limit 100",
|
||||
"refId": "A",
|
||||
"select": [
|
||||
[
|
||||
{
|
||||
"params": [
|
||||
"value"
|
||||
],
|
||||
"type": "column"
|
||||
}
|
||||
]
|
||||
],
|
||||
"timeColumn": "time",
|
||||
"where": [
|
||||
{
|
||||
"name": "$__timeFilter",
|
||||
"params": [],
|
||||
"type": "macro"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Last queries",
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 30,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"allValue": "",
|
||||
"current": {},
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"definition": "select distinct client_name from log_entries",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Client name",
|
||||
"multi": true,
|
||||
"name": "client_name",
|
||||
"options": [],
|
||||
"query": "select distinct client_name from log_entries",
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"allValue": null,
|
||||
"current": {},
|
||||
"datasource": "${DS_POSTGRES}",
|
||||
"definition": "select distinct response_type from log_entries",
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Response type",
|
||||
"multi": true,
|
||||
"name": "response_type",
|
||||
"options": [],
|
||||
"query": "select distinct response_type from log_entries",
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"tagValuesQuery": "",
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "",
|
||||
"value": ""
|
||||
},
|
||||
"description": null,
|
||||
"error": null,
|
||||
"hide": 0,
|
||||
"label": "Domain (contains)",
|
||||
"name": "question",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "",
|
||||
"value": ""
|
||||
}
|
||||
],
|
||||
"query": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "textbox"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-24h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Blocky query",
|
||||
"uid": "AVmWSVWgz",
|
||||
"version": 3
|
||||
}
|
|
@ -19,6 +19,14 @@ upstream:
|
|||
# optional: timeout to query the upstream resolver. Default: 2s
|
||||
upstreamTimeout: 2s
|
||||
|
||||
# optional: If true, blocky will fail to start unless at least one upstream server per group is reachable. Default: false
|
||||
startVerifyUpstream: true
|
||||
|
||||
# optional: Determines how blocky will create outgoing connections. This impacts both upstreams, and lists.
|
||||
# accepted: dual, v4, v6
|
||||
# default: dual
|
||||
connectIPVersion: dual
|
||||
|
||||
# optional: custom IP address(es) for domain name (with all sub-domains). Multiple addresses must be separated by a comma
|
||||
# example: query "printer.lan" or "my.printer.lan" will return 192.168.178.3
|
||||
customDNS:
|
||||
|
@ -35,6 +43,10 @@ customDNS:
|
|||
# optional: definition, which DNS resolver(s) should be used for queries to the domain (with all sub-domains). Multiple resolvers must be separated by a comma
|
||||
# Example: Query client.fritz.box will ask DNS server 192.168.178.1. This is necessary for local network, to resolve clients by host name
|
||||
conditional:
|
||||
# optional: if false (default), return empty result if after rewrite, the mapped resolver returned an empty answer. If true, the original query will be sent to the upstream resolver
|
||||
# Example: The query "blog.example.com" will be rewritten to "blog.fritz.box" and also redirected to the resolver at 192.168.178.1. If not found and if `fallbackUpstream` was set to `true`, the original query "blog.example.com" will be sent upstream.
|
||||
# Usage: One usecase when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain.
|
||||
fallbackUpstream: false
|
||||
# optional: replace domain in the query with other domain before resolver lookup in the mapping
|
||||
rewrite:
|
||||
example.com: fritz.box
|
||||
|
@ -97,8 +109,8 @@ blocking:
|
|||
downloadAttempts: 5
|
||||
# optional: Time between the download attempts. Default: 1s
|
||||
downloadCooldown: 10s
|
||||
# optional: if true, application startup will fail if at least one list can't be downloaded / opened. Default: false
|
||||
failStartOnListError: false
|
||||
# optional: if failOnError, application startup will fail if at least one list can't be downloaded / opened. Default: blocking
|
||||
startStrategy: failOnError
|
||||
|
||||
# optional: configuration for caching of DNS responses
|
||||
caching:
|
||||
|
@ -128,6 +140,9 @@ caching:
|
|||
# Max number of domains to be kept in cache for prefetching (soft limit). Useful on systems with limited amount of RAM.
|
||||
# Default (0): unlimited
|
||||
prefetchMaxItemsCount: 0
|
||||
# Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results.
|
||||
# Default: 30m
|
||||
cacheTimeNegative: 30m
|
||||
|
||||
# optional: configuration of client name resolution
|
||||
clientLookup:
|
||||
|
@ -206,6 +221,8 @@ hostsFile:
|
|||
hostsTTL: 60m
|
||||
# optional: Time between hosts file refresh, default: 1h
|
||||
refreshPeriod: 30m
|
||||
# optional: Whether loopback hosts addresses (127.0.0.0/8 and ::1) should be filtered or not, default: false
|
||||
filterLoopback: true
|
||||
# optional: Log level (one from debug, info, warn, error). Default: info
|
||||
logLevel: info
|
||||
# optional: Log format (text or json). Default: text
|
||||
|
@ -214,3 +231,8 @@ logFormat: text
|
|||
logTimestamp: true
|
||||
# optional: obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. Default: false
|
||||
logPrivacy: false
|
||||
|
||||
# optional: add EDE error codes to dns response
|
||||
ede:
|
||||
# enabled if true, Default: false
|
||||
enable: true
|
|
@ -25,6 +25,8 @@ configuration properties as [JSON](config.yml).
|
|||
| logPrivacy | bool | no | false | Obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. |
|
||||
| dohUserAgent | string | no | | HTTP User Agent for DoH upstreams |
|
||||
| minTlsServeVersion | string | no | 1.2 | Minimum TLS version that the DoT and DoH server use to serve those encrypted DNS requests |
|
||||
| startVerifyUpstream | bool | no | false | If true, blocky will fail to start unless at least one upstream server per group is reachable. |
|
||||
| connectIPVersion | bool | no | dual | IP version to use for outgoing connections (dual, v4, v6) |
|
||||
|
||||
!!! example
|
||||
|
||||
|
@ -50,13 +52,16 @@ following network protocols (net part of the resolver URL):
|
|||
returns the answer from the fastest one. This improves your network speed and increases your privacy - your DNS traffic
|
||||
will be distributed over multiple providers.
|
||||
|
||||
Each resolver must be defined as a string in following format: `[net:]host:[port][/path]`.
|
||||
Each resolver must be defined as a string in following format: `[net:]host:[port][/path][#commonName]`.
|
||||
|
||||
| Parameter | Type | Mandatory | Default value |
|
||||
|-----------|----------------------------------|-----------|---------------------------------------------------|
|
||||
| net | enum (tcp+udp, tcp-tls or https) | no | tcp+udp |
|
||||
| host | IP or hostname | yes | |
|
||||
| port | int (1 - 65535) | no | 53 for udp/tcp, 853 for tcp-tls and 443 for https |
|
||||
| net | enum (tcp+udp, tcp-tls or https) | no | tcp+udp |
|
||||
| host | IP or hostname | yes | |
|
||||
| port | int (1 - 65535) | no | 53 for udp/tcp, 853 for tcp-tls and 443 for https |
|
||||
| commonName | string | no | the host value |
|
||||
|
||||
The commonName parameter overrides the expected certificate common name value used for verification.
|
||||
|
||||
Blocky needs at least the configuration of the **default** group. This group will be used as a fallback, if no client
|
||||
specific resolver configuration is available.
|
||||
|
@ -134,7 +139,6 @@ Works only on Linux/\*nix OS due to golang limitations under Windows.
|
|||
- 123.123.123.123
|
||||
```
|
||||
|
||||
|
||||
## Filtering
|
||||
|
||||
Under certain circumstances, it may be useful to filter some types of DNS queries. You can define one or more DNS query
|
||||
|
@ -150,6 +154,18 @@ types, all queries with these types will be dropped (empty answer will be return
|
|||
|
||||
This configuration will drop all 'AAAA' (IPv6) queries.
|
||||
|
||||
## FQDN only
|
||||
|
||||
In domain environments, it may be usefull to only response to FQDN requests. If this option is enabled blocky respond immidiatly
|
||||
with NXDOMAIN if the request is not a valid FQDN. The request is therfore not further processed by other options like custom or conditional.
|
||||
Please be aware that by enabling it your hostname resolution will break unless every hostname is part of a domain.
|
||||
|
||||
!!! example
|
||||
|
||||
```yaml
|
||||
fqdnOnly: true
|
||||
```
|
||||
|
||||
## Custom DNS
|
||||
|
||||
You can define your own domain name to IP mappings. For example, you can use a user-friendly name for a network printer
|
||||
|
@ -186,7 +202,7 @@ The query "printer.home" will be rewritten to "printer.lan" and return 192.168.1
|
|||
|
||||
With parameter `filterUnmappedTypes = true` (default), blocky will filter all queries with unmapped types, for example:
|
||||
AAAA for "printer.lan" or TXT for "otherdevice.lan".
|
||||
With `filterUnmappedTypes = true` a query AAAA "printer.lan" will be forwarded to the upstream DNS server.
|
||||
With `filterUnmappedTypes = false` a query AAAA "printer.lan" will be forwarded to the upstream DNS server.
|
||||
|
||||
## Conditional DNS resolution
|
||||
|
||||
|
@ -196,10 +212,15 @@ hostname belongs to which IP address, all DNS queries for the local network shou
|
|||
|
||||
The optional parameter `rewrite` behaves the same as with custom DNS.
|
||||
|
||||
The optional parameter fallbackUpstream, if false (default), return empty result if after rewrite, the mapped resolver returned an empty answer. If true, the original query will be sent to the upstream resolver.
|
||||
|
||||
### Usage: One usecase when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain
|
||||
|
||||
!!! example
|
||||
|
||||
```yaml
|
||||
conditional:
|
||||
fallbackUpstream: false
|
||||
rewrite:
|
||||
example.com: fritz.box
|
||||
replace-me.com: with-this.com
|
||||
|
@ -217,9 +238,13 @@ The optional parameter `rewrite` behaves the same as with custom DNS.
|
|||
You can use `.` as wildcard for all non full qualified domains (domains without dot)
|
||||
|
||||
In this example, a DNS query "client.fritz.box" will be redirected to the router's DNS server at 192.168.178.1 and client.lan.net to 192.170.1.2 and 192.170.1.3.
|
||||
The query "client.example.com" will be rewritten to "client.fritz.box" and also redirected to the resolver at 192.168.178.1. All unqualified hostnames (e.g. "test")
|
||||
will be redirected to the DNS server at 168.168.0.1
|
||||
The query "client.example.com" will be rewritten to "client.fritz.box" and also redirected to the resolver at 192.168.178.1.
|
||||
|
||||
If not found and if `fallbackUpstream` was set to `true`, the original query "blog.example.com" will be sent upstream.
|
||||
|
||||
All unqualified hostnames (e.g. "test") will be redirected to the DNS server at 168.168.0.1.
|
||||
|
||||
One usecase for `fallbackUpstream` is when having split DNS for internal and external (internet facing) users, but not all subdomains are listed in the internal domain.
|
||||
|
||||
## Client name lookup
|
||||
|
||||
|
@ -436,16 +461,22 @@ You can configure the list download attempts according to your internet connecti
|
|||
downloadCooldown: 10s
|
||||
```
|
||||
|
||||
### Fail on start
|
||||
### Start strategy
|
||||
|
||||
You can ensure with parameter `failStartOnListError = true` that the application will fail if at least one list can't be
|
||||
downloaded or opened. Default value is `false`.
|
||||
You can configure the blocking behavior during application start of blocky.
|
||||
If no starategy is selected blocking will be used.
|
||||
|
||||
| startStrategy | Description |
|
||||
|---------------|-------------------------------------------------------------------------------------------------------|
|
||||
| blocking | all blocking lists will be loaded before DNS resoulution starts |
|
||||
| failOnError | like blocking but blocky shutsdown if an download fails |
|
||||
| fast | DNS resolution starts immediately without blocking which will be enabled after list load is completed |
|
||||
|
||||
!!! example
|
||||
|
||||
```yaml
|
||||
blocking:
|
||||
failStartOnListError: false
|
||||
startStrategy: failOnError
|
||||
```
|
||||
|
||||
### Concurrency
|
||||
|
@ -454,7 +485,7 @@ Blocky downloads and processes links in a single group concurrently. With parame
|
|||
how many links can be processed in the same time. Higher value can reduce the overall list refresh time, but more parallel
|
||||
download and processing jobs need more RAM. Please consider to reduce this value on systems with limited memory. Default value is 4.
|
||||
|
||||
!!! example
|
||||
!!! example
|
||||
|
||||
```yaml
|
||||
blocking:
|
||||
|
@ -483,7 +514,7 @@ With following parameters you can tune the caching behavior:
|
|||
| caching.prefetchExpires | duration format | no | 2h | Prefetch track time window |
|
||||
| caching.prefetchThreshold | int | no | 5 | Name queries threshold for prefetch |
|
||||
| caching.prefetchMaxItemsCount | int | no | 0 (unlimited) | Max number of domains to be kept in cache for prefetching (soft limit). Default (0): unlimited. Useful on systems with limited amount of RAM. |
|
||||
| caching.cacheTimeNegative | duration format | no | 30m | Time how long negative results are cached. A value of -1 will disable caching for negative results. |
|
||||
| caching.cacheTimeNegative | duration format | no | 30m | Time how long negative results (NXDOMAIN response or empty result) are cached. A value of -1 will disable caching for negative results. |
|
||||
|
||||
!!! example
|
||||
|
||||
|
@ -593,17 +624,18 @@ example for Database
|
|||
logRetentionDays: 7
|
||||
```
|
||||
|
||||
### Hosts file
|
||||
## Hosts file
|
||||
|
||||
You can enable resolving of entries, located in local hosts file.
|
||||
|
||||
Configuration parameters:
|
||||
|
||||
| Parameter | Type | Mandatory | Default value | Description |
|
||||
|--------------------------|--------------------------------|-----------|---------------|-----------------------------------------------|
|
||||
| hostsFile.filePath | string | no | | Path to hosts file (e.g. /etc/hosts on Linux) |
|
||||
| hostsFile.hostsTTL | duration (no units is minutes) | no | 1h | TTL |
|
||||
| hostsFile.refreshPeriod | duration format | no | 1h | Time between hosts file refresh |
|
||||
| Parameter | Type | Mandatory | Default value | Description |
|
||||
|--------------------------|--------------------------------|-----------|---------------|--------------------------------------------------|
|
||||
| hostsFile.filePath | string | no | | Path to hosts file (e.g. /etc/hosts on Linux) |
|
||||
| hostsFile.hostsTTL | duration (no units is minutes) | no | 1h | TTL |
|
||||
| hostsFile.refreshPeriod | duration format | no | 1h | Time between hosts file refresh |
|
||||
| hostsFile.filterLoopback | bool | no | false | Filter loopback addresses (127.0.0.0/8 and ::1) |
|
||||
|
||||
!!! example
|
||||
|
||||
|
@ -614,6 +646,23 @@ Configuration parameters:
|
|||
refreshPeriod: 30m
|
||||
```
|
||||
|
||||
### Deliver EDE codes as EDNS0 option
|
||||
|
||||
DNS responses can be extended with EDE codes according to [RFC8914](https://datatracker.ietf.org/doc/rfc8914/).
|
||||
|
||||
Configuration parameters:
|
||||
|
||||
| Parameter | Type | Mandatory | Default value | Description |
|
||||
|--------------------------|--------------------------------|-----------|---------------|----------------------------------------------------|
|
||||
| ede.enable | bool | no | false | If true, DNS responses are deliverd with EDE codes |
|
||||
|
||||
!!! example
|
||||
|
||||
```yaml
|
||||
ede:
|
||||
enable: true
|
||||
```
|
||||
|
||||
## SSL certificate configuration (DoH / TLS listener)
|
||||
|
||||
See [Wiki - Configuration of HTTPS](https://github.com/0xERR0R/blocky/wiki/Configuration-of-HTTPS-for-DoH-and-Rest-API)
|
||||
|
|
|
@ -52,4 +52,8 @@ or [at grafana.com](https://grafana.com/grafana/dashboards/14980)
|
|||
|
||||
Please define the MySQL source in Grafana, which points to the database with blocky's log entries.
|
||||
|
||||
## Postgres
|
||||
|
||||
The JSON for a Grafana dashboard equivalent to the MySQL/MariaDB version is located [here](https://github.com/0xERR0R/blocky/blob/master/docs/blocky-query-grafana-postgres.json)
|
||||
|
||||
--8<-- "docs/includes/abbreviations.md"
|
||||
|
|
116
go.mod
116
go.mod
|
@ -1,90 +1,112 @@
|
|||
module github.com/0xERR0R/blocky
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/alicebob/miniredis/v2 v2.21.0
|
||||
github.com/abice/go-enum v0.5.3
|
||||
github.com/alicebob/miniredis/v2 v2.23.1
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
|
||||
github.com/avast/retry-go/v4 v4.3.0
|
||||
github.com/creasty/defaults v1.6.0
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/dns v1.1.49
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/mroth/weightedrand v0.4.1
|
||||
github.com/onsi/gomega v1.19.0
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/stretchr/testify v1.7.2
|
||||
github.com/onsi/ginkgo/v2 v2.5.0
|
||||
github.com/onsi/gomega v1.24.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/swaggo/swag v1.8.7
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
golang.org/x/net v0.2.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gorm.io/driver/mysql v1.3.4
|
||||
gorm.io/driver/sqlite v1.3.2
|
||||
gorm.io/gorm v1.23.5
|
||||
gorm.io/driver/mysql v1.4.3
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
gorm.io/driver/sqlite v1.4.3
|
||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755
|
||||
)
|
||||
|
||||
require github.com/dosgo/zigtool v0.0.0-20210923085854-9c6fc1d62198
|
||||
|
||||
require (
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/avast/retry-go/v4 v4.0.5
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/onsi/ginkgo/v2 v2.1.4
|
||||
github.com/swaggo/swag v1.8.2
|
||||
gorm.io/driver/postgres v1.3.7
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||
github.com/go-openapi/spec v0.20.7 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.12.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
|
||||
github.com/jackc/pgtype v1.11.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.16.1 // indirect
|
||||
github.com/jackc/pgtype v1.12.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.17.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.12 // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/mattn/goveralls v0.0.11 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/ramr/go-reaper v0.2.1
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.23.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
golang.org/x/term v0.2.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
259
go.sum
259
go.sum
|
@ -35,30 +35,34 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/abice/go-enum v0.5.3 h1:Ghq0aWp+tCNZFAb4lFK7UnjzUJQTS1atIMjHkX+Gex4=
|
||||
github.com/abice/go-enum v0.5.3/go.mod h1:jf915DI7NxXZRwk8qDgZJKq2raAtwcPBXJRh9WVgtU0=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.21.0 h1:CdmwIlKUWFBDS+4464GtQiQ0R1vpzOgu4Vnd74rBL7M=
|
||||
github.com/alicebob/miniredis/v2 v2.21.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
|
||||
github.com/alicebob/miniredis/v2 v2.23.1 h1:jR6wZggBxwWygeXcdNyguCOCIjPsZyNUNlAkTx2fu0U=
|
||||
github.com/alicebob/miniredis/v2 v2.23.1/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM=
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=
|
||||
github.com/avast/retry-go/v4 v4.0.5 h1:C0Fm9MjPCmgLW6Jb1zBTVRx0ycr+VUaaUZO5wpqYjqg=
|
||||
github.com/avast/retry-go/v4 v4.0.5/go.mod h1:HqmLvS2VLdStPCGDFjSuZ9pzlTqVRldCI4w2dO4m1Ms=
|
||||
github.com/avast/retry-go/v4 v4.3.0 h1:cqI48aXx0BExKoM7XPklDpoHAg7/srPPLAfWG5z62jo=
|
||||
github.com/avast/retry-go/v4 v4.3.0/go.mod h1:bqOlT4nxk4phk9buiQFaghzjpqdchOSwPgjdfdQBtdg=
|
||||
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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
|
@ -72,7 +76,8 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
|
|||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creasty/defaults v1.6.0 h1:ltuE9cfphUtlrBeomuu8PEyISTXnxqkBIoQfXgv7BSc=
|
||||
|
@ -82,11 +87,16 @@ 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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dosgo/zigtool v0.0.0-20210923085854-9c6fc1d62198 h1:3b37D/Oxs95GmDsGKNx21aBYWF270emHjqUExsAL01g=
|
||||
github.com/dosgo/zigtool v0.0.0-20210923085854-9c6fc1d62198/go.mod h1:NUrh34aXXgbs4C2HkTmRmkzsKhtrFPRitYkbZMDDONo=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
|
@ -97,24 +107,31 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
|||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
|
||||
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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||
github.com/go-openapi/spec v0.20.7 h1:1Rlu/ZrOCCob0n+JKKJAWhNWMPW8bOZRg8FJaY+0SKI=
|
||||
github.com/go-openapi/spec v0.20.7/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
|
@ -129,6 +146,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -156,8 +175,9 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
|
@ -168,6 +188,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
@ -184,9 +206,14 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
|||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
|
@ -197,8 +224,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
|
|||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
|
||||
github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
|
||||
github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
|
||||
github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
|
@ -214,26 +241,26 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
|
|||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
|
||||
github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
|
||||
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
|
||||
github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
|
||||
github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
|
||||
github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
|
||||
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
|
||||
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
|
@ -256,43 +283,50 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv
|
|||
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/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/goveralls v0.0.11 h1:eJXea6R6IFlL1QMKNMzDvvHv/hwGrnvyig4N+0+XiMM=
|
||||
github.com/mattn/goveralls v0.0.11/go.mod h1:gU8SyhNswsJKchEV93xRQxX6X3Ei4PJdQk/6ZHvrvRk=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
|
@ -305,11 +339,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW
|
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
|
||||
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk=
|
||||
github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls=
|
||||
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
|
||||
github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E=
|
||||
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
|
||||
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=
|
||||
|
@ -320,29 +353,36 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
|
|||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34=
|
||||
github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
|
||||
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s=
|
||||
github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/ramr/go-reaper v0.2.1 h1:zww+wlQOvTjBZuk1920R/e0GFEb6O7+B0WQLV6dM924=
|
||||
github.com/ramr/go-reaper v0.2.1/go.mod h1:AVypdzrcCXjSc/JYnlXl8TsB+z84WyFzxWE8Jh0MOJc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
|
@ -352,36 +392,44 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
|||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
|
||||
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/swaggo/swag v1.8.2 h1:D4aBiVS2a65zhyk3WFqOUz7Rz0sOaUcgeErcid5uGL4=
|
||||
github.com/swaggo/swag v1.8.2/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
|
||||
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||
github.com/urfave/cli/v2 v2.23.0 h1:pkly7gKIeYv3olPAeNajNpLjeJrmTPYCoZWaV+2VfvE=
|
||||
github.com/urfave/cli/v2 v2.23.0/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
|
@ -410,9 +458,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
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=
|
||||
|
@ -444,8 +492,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -477,18 +525,20 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -498,8 +548,9 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -540,25 +591,25 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
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=
|
||||
|
@ -566,8 +617,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -616,16 +668,15 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
|
|||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
@ -702,15 +753,14 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
@ -723,17 +773,20 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/mysql v1.3.4 h1:/KoBMgsUHC3bExsekDcmNYaBnfH2WNeFuXqqrqMc98Q=
|
||||
gorm.io/driver/mysql v1.3.4/go.mod h1:s4Tq0KmD0yhPGHbZEwg1VPlH0vT/GBHJZorPzhcxBUE=
|
||||
gorm.io/driver/postgres v1.3.7 h1:FKF6sIMDHDEvvMF/XJvbnCl0nu6KSKUaPXevJ4r+VYQ=
|
||||
gorm.io/driver/postgres v1.3.7/go.mod h1:f02ympjIcgtHEGFMZvdgTxODZ9snAHDb4hXfigBVuNI=
|
||||
gorm.io/driver/sqlite v1.3.2 h1:nWTy4cE52K6nnMhv23wLmur9Y3qWbZvOBz+V4PrGAxg=
|
||||
gorm.io/driver/sqlite v1.3.2/go.mod h1:B+8GyC9K7VgzJAcrcXMRPdnMcck+8FgJynEehEPM16U=
|
||||
gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=
|
||||
gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
|
||||
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||
gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
|
||||
gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
|
||||
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755 h1:7AdrbfcvKnzejfqP5g37fdSZOXH/JvaPIzBIHTOqXKk=
|
||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
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=
|
||||
|
|
|
@ -3,7 +3,6 @@ package helpertest
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
|
@ -16,7 +15,7 @@ import (
|
|||
|
||||
// TempFile creates temp file with passed data
|
||||
func TempFile(data string) *os.File {
|
||||
f, err := ioutil.TempFile("", "prefix")
|
||||
f, err := os.CreateTemp("", "prefix")
|
||||
if err != nil {
|
||||
log.Log().Fatal(err)
|
||||
}
|
||||
|
@ -40,7 +39,8 @@ func TestServer(data string) *httptest.Server {
|
|||
}
|
||||
|
||||
// DoGetRequest performs a GET request
|
||||
func DoGetRequest(url string, fn func(w http.ResponseWriter, r *http.Request)) (code int, body *bytes.Buffer) {
|
||||
func DoGetRequest(url string,
|
||||
fn func(w http.ResponseWriter, r *http.Request)) (*httptest.ResponseRecorder, *bytes.Buffer) {
|
||||
r, _ := http.NewRequest("GET", url, nil)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
@ -48,7 +48,7 @@ func DoGetRequest(url string, fn func(w http.ResponseWriter, r *http.Request)) (
|
|||
|
||||
handler.ServeHTTP(rr, r)
|
||||
|
||||
return rr.Code, rr.Body
|
||||
return rr, rr.Body
|
||||
}
|
||||
|
||||
// BeDNSRecord returns new dns matcher
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
package helpertest
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type TmpFolder struct {
|
||||
Path string
|
||||
Error error
|
||||
prefix string
|
||||
}
|
||||
|
||||
type TmpFile struct {
|
||||
Path string
|
||||
Error error
|
||||
Folder *TmpFolder
|
||||
}
|
||||
|
||||
func NewTmpFolder(prefix string) *TmpFolder {
|
||||
ipref := prefix
|
||||
|
||||
if len(ipref) == 0 {
|
||||
ipref = "blocky"
|
||||
}
|
||||
|
||||
path, err := os.MkdirTemp("", ipref)
|
||||
|
||||
res := &TmpFolder{
|
||||
Path: path,
|
||||
Error: err,
|
||||
prefix: ipref,
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) Clean() error {
|
||||
if len(tf.Path) > 0 {
|
||||
return os.RemoveAll(tf.Path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) CreateSubFolder(name string) *TmpFolder {
|
||||
var path string
|
||||
|
||||
var err error
|
||||
|
||||
if len(name) > 0 {
|
||||
path = filepath.Join(tf.Path, name)
|
||||
err = os.Mkdir(path, fs.ModePerm)
|
||||
} else {
|
||||
path, err = os.MkdirTemp(tf.Path, tf.prefix)
|
||||
}
|
||||
|
||||
res := &TmpFolder{
|
||||
Path: path,
|
||||
Error: err,
|
||||
prefix: tf.prefix,
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) CreateEmptyFile(name string) *TmpFile {
|
||||
f, err := tf.createFile(name)
|
||||
|
||||
if err != nil {
|
||||
return tf.newErrorTmpFile(err)
|
||||
}
|
||||
|
||||
return tf.checkState(f, err)
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) CreateStringFile(name string, lines ...string) *TmpFile {
|
||||
f, err := tf.createFile(name)
|
||||
|
||||
if err != nil {
|
||||
return tf.newErrorTmpFile(err)
|
||||
}
|
||||
|
||||
first := true
|
||||
|
||||
w := bufio.NewWriter(f)
|
||||
|
||||
for _, l := range lines {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
_, err = w.WriteString("\n")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
_, err = w.WriteString(l)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
return tf.checkState(f, err)
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) JoinPath(name string) string {
|
||||
return filepath.Join(tf.Path, name)
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) CountFiles() (int, error) {
|
||||
files, err := os.ReadDir(tf.Path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return len(files), nil
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) createFile(name string) (*os.File, error) {
|
||||
if len(name) > 0 {
|
||||
return os.Create(filepath.Join(tf.Path, name))
|
||||
}
|
||||
|
||||
return os.CreateTemp(tf.Path, "temp")
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) newErrorTmpFile(err error) *TmpFile {
|
||||
return &TmpFile{
|
||||
Path: "",
|
||||
Error: err,
|
||||
Folder: tf,
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *TmpFolder) checkState(file *os.File, ierr error) *TmpFile {
|
||||
err := ierr
|
||||
filepath := ""
|
||||
|
||||
if file != nil {
|
||||
filepath = file.Name()
|
||||
|
||||
file.Close()
|
||||
|
||||
_, err = os.Stat(filepath)
|
||||
}
|
||||
|
||||
return &TmpFile{
|
||||
Path: filepath,
|
||||
Error: err,
|
||||
Folder: tf,
|
||||
}
|
||||
}
|
||||
|
||||
func (tf *TmpFile) Stat() error {
|
||||
if tf.Error != nil {
|
||||
return tf.Error
|
||||
}
|
||||
|
||||
_, res := os.Stat(tf.Path)
|
||||
|
||||
return res
|
||||
}
|
|
@ -183,9 +183,10 @@ var _ = Describe("Downloader", func() {
|
|||
It("Should perform a retry until max retry attempt count is reached and return TransientError", func() {
|
||||
reader, err := sut.DownloadFile(server.URL)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
var transientErr *TransientError
|
||||
Expect(errors.As(err, &transientErr)).To(BeTrue())
|
||||
Expect(transientErr.Unwrap().Error()).Should(ContainSubstring("Timeout"))
|
||||
|
||||
err2 := unwrapTransientErr(err)
|
||||
|
||||
Expect(err2.Error()).Should(ContainSubstring("Timeout"))
|
||||
Expect(reader).Should(BeNil())
|
||||
|
||||
// failed download event was emitted 3 times
|
||||
|
@ -196,15 +197,18 @@ var _ = Describe("Downloader", func() {
|
|||
When("DNS resolution of passed URL fails", func() {
|
||||
BeforeEach(func() {
|
||||
sut = NewDownloader(
|
||||
WithTimeout(100*time.Millisecond),
|
||||
WithTimeout(500*time.Millisecond),
|
||||
WithAttempts(3),
|
||||
WithCooldown(time.Millisecond))
|
||||
WithCooldown(200*time.Millisecond))
|
||||
})
|
||||
It("Should perform a retry until max retry attempt count is reached and return DNSError", func() {
|
||||
reader, err := sut.DownloadFile("http://some.domain.which.does.not.exist")
|
||||
Expect(err).Should(HaveOccurred())
|
||||
|
||||
err2 := unwrapTransientErr(err)
|
||||
|
||||
var dnsError *net.DNSError
|
||||
Expect(errors.As(err, &dnsError)).To(BeTrue(), "received error %w", err)
|
||||
Expect(errors.As(err2, &dnsError)).To(BeTrue(), "received error %w", err)
|
||||
Expect(reader).Should(BeNil())
|
||||
|
||||
// failed download event was emitted 3 times
|
||||
|
@ -215,3 +219,12 @@ var _ = Describe("Downloader", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
func unwrapTransientErr(origErr error) error {
|
||||
var transientErr *TransientError
|
||||
if errors.As(origErr, &transientErr) {
|
||||
return transientErr.Unwrap()
|
||||
}
|
||||
|
||||
return origErr
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package lists
|
||||
|
||||
//go:generate go-enum -f=$GOFILE --marshal --names
|
||||
//go:generate go run github.com/abice/go-enum -f=$GOFILE --marshal --names
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
|
@ -95,7 +95,7 @@ func (b *ListCache) Configuration() (result []string) {
|
|||
|
||||
// NewListCache creates new list instance
|
||||
func NewListCache(t ListCacheType, groupToLinks map[string][]string, refreshPeriod time.Duration,
|
||||
downloader FileDownloader, processingConcurrency uint) (*ListCache, error) {
|
||||
downloader FileDownloader, processingConcurrency uint, async bool) (*ListCache, error) {
|
||||
groupCaches := make(map[string]stringcache.StringCache)
|
||||
|
||||
if processingConcurrency == 0 {
|
||||
|
@ -110,7 +110,13 @@ func NewListCache(t ListCacheType, groupToLinks map[string][]string, refreshPeri
|
|||
listType: t,
|
||||
processingConcurrency: processingConcurrency,
|
||||
}
|
||||
initError := b.refresh(true)
|
||||
|
||||
var initError error
|
||||
if async {
|
||||
initError = nil
|
||||
} else {
|
||||
initError = b.refresh(true)
|
||||
}
|
||||
|
||||
if initError == nil {
|
||||
go periodicUpdate(b)
|
||||
|
|
|
@ -12,7 +12,7 @@ func BenchmarkRefresh(b *testing.B) {
|
|||
"gr1": {file1, file2, file3},
|
||||
}
|
||||
|
||||
cache, _ := NewListCache(ListCacheTypeBlacklist, lists, -1, NewDownloader(), 5)
|
||||
cache, _ := NewListCache(ListCacheTypeBlacklist, lists, -1, NewDownloader(), 5, false)
|
||||
|
||||
b.ReportAllocs()
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
ListCacheTypeWhitelist
|
||||
)
|
||||
|
||||
var ErrInvalidListCacheType = fmt.Errorf("not a valid ListCacheType, try [%s]", strings.Join(_ListCacheTypeNames, ", "))
|
||||
|
||||
const _ListCacheTypeName = "blacklistwhitelist"
|
||||
|
||||
var _ListCacheTypeNames = []string{
|
||||
|
@ -57,7 +59,7 @@ func ParseListCacheType(name string) (ListCacheType, error) {
|
|||
if x, ok := _ListCacheTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return ListCacheType(0), fmt.Errorf("%s is not a valid ListCacheType, try [%s]", name, strings.Join(_ListCacheTypeNames, ", "))
|
||||
return ListCacheType(0), fmt.Errorf("%s is %w", name, ErrInvalidListCacheType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http/httptest"
|
||||
|
@ -22,36 +21,39 @@ import (
|
|||
|
||||
var _ = Describe("ListCache", func() {
|
||||
var (
|
||||
emptyFile, file1, file2, file3 *os.File
|
||||
tmpDir *TmpFolder
|
||||
emptyFile, file1, file2, file3 *TmpFile
|
||||
server1, server2, server3 *httptest.Server
|
||||
)
|
||||
BeforeEach(func() {
|
||||
emptyFile = TempFile("#empty file\n\n")
|
||||
server1 = TestServer("blocked1.com\nblocked1a.com\n192.168.178.55")
|
||||
server2 = TestServer("blocked2.com")
|
||||
server3 = TestServer("blocked3.com\nblocked1a.com")
|
||||
tmpDir = NewTmpFolder("ListCache")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
|
||||
file1 = TempFile("blocked1.com\nblocked1a.com")
|
||||
file2 = TempFile("blocked2.com")
|
||||
file3 = TempFile("blocked3.com\nblocked1a.com")
|
||||
})
|
||||
AfterEach(func() {
|
||||
_ = os.Remove(emptyFile.Name())
|
||||
_ = os.Remove(file1.Name())
|
||||
_ = os.Remove(file2.Name())
|
||||
_ = os.Remove(file3.Name())
|
||||
server1.Close()
|
||||
server2.Close()
|
||||
server3.Close()
|
||||
server1 = TestServer("blocked1.com\nblocked1a.com\n192.168.178.55")
|
||||
DeferCleanup(server1.Close)
|
||||
server2 = TestServer("blocked2.com")
|
||||
DeferCleanup(server2.Close)
|
||||
server3 = TestServer("blocked3.com\nblocked1a.com")
|
||||
DeferCleanup(server3.Close)
|
||||
|
||||
emptyFile = tmpDir.CreateStringFile("empty", "#empty file")
|
||||
Expect(emptyFile.Error).Should(Succeed())
|
||||
file1 = tmpDir.CreateStringFile("file1", "blocked1.com", "blocked1a.com")
|
||||
Expect(file1.Error).Should(Succeed())
|
||||
file2 = tmpDir.CreateStringFile("file2", "blocked2.com")
|
||||
Expect(file2.Error).Should(Succeed())
|
||||
file3 = tmpDir.CreateStringFile("file3", "blocked3.com", "blocked1a.com")
|
||||
Expect(file3.Error).Should(Succeed())
|
||||
})
|
||||
|
||||
Describe("List cache and matching", func() {
|
||||
When("Query with empty", func() {
|
||||
It("should not panic", func() {
|
||||
lists := map[string][]string{
|
||||
"gr0": {emptyFile.Name()},
|
||||
"gr0": {emptyFile.Path},
|
||||
}
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
found, group := sut.Match("", []string{"gr0"})
|
||||
|
@ -63,9 +65,9 @@ var _ = Describe("ListCache", func() {
|
|||
When("List is empty", func() {
|
||||
It("should not match anything", func() {
|
||||
lists := map[string][]string{
|
||||
"gr1": {emptyFile.Name()},
|
||||
"gr1": {emptyFile.Path},
|
||||
}
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
found, group := sut.Match("google.com", []string{"gr1"})
|
||||
|
@ -79,12 +81,15 @@ var _ = Describe("ListCache", func() {
|
|||
// should produce a transient error on second and third attempt
|
||||
data := make(chan func() (io.ReadCloser, error), 3)
|
||||
mockDownloader := &MockDownloader{data: data}
|
||||
// nolint:unparam
|
||||
data <- func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("blocked1.com")), nil
|
||||
}
|
||||
// nolint:unparam
|
||||
data <- func() (io.ReadCloser, error) {
|
||||
return nil, &TransientError{inner: errors.New("boom")}
|
||||
}
|
||||
// nolint:unparam
|
||||
data <- func() (io.ReadCloser, error) {
|
||||
return nil, &TransientError{inner: errors.New("boom")}
|
||||
}
|
||||
|
@ -97,6 +102,7 @@ var _ = Describe("ListCache", func() {
|
|||
4*time.Hour,
|
||||
mockDownloader,
|
||||
defaultProcessingConcurrency,
|
||||
false,
|
||||
)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
|
@ -131,6 +137,7 @@ var _ = Describe("ListCache", func() {
|
|||
// should produce a 404 err on second attempt
|
||||
data := make(chan func() (io.ReadCloser, error), 2)
|
||||
mockDownloader := &MockDownloader{data: data}
|
||||
//nolint:unparam
|
||||
data <- func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("blocked1.com")), nil
|
||||
}
|
||||
|
@ -141,7 +148,8 @@ var _ = Describe("ListCache", func() {
|
|||
"gr1": {"http://dummy"},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, mockDownloader, defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, mockDownloader,
|
||||
defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
By("Lists loaded without err", func() {
|
||||
|
@ -171,7 +179,7 @@ var _ = Describe("ListCache", func() {
|
|||
"gr2": {server3.URL},
|
||||
}
|
||||
|
||||
sut, _ := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, _ := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency, false)
|
||||
|
||||
found, group := sut.Match("blocked1.com", []string{"gr1", "gr2"})
|
||||
Expect(found).Should(BeTrue())
|
||||
|
@ -193,7 +201,7 @@ var _ = Describe("ListCache", func() {
|
|||
"gr2": {server3.URL, "someotherfile"},
|
||||
}
|
||||
|
||||
sut, _ := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, _ := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency, false)
|
||||
|
||||
found, group := sut.Match("blocked1.com", []string{"gr1", "gr2"})
|
||||
Expect(found).Should(BeTrue())
|
||||
|
@ -220,7 +228,7 @@ var _ = Describe("ListCache", func() {
|
|||
resultCnt = cnt
|
||||
})
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
found, group := sut.Match("blocked1.com", []string{})
|
||||
|
@ -232,11 +240,11 @@ var _ = Describe("ListCache", func() {
|
|||
When("multiple groups are passed", func() {
|
||||
It("should match", func() {
|
||||
lists := map[string][]string{
|
||||
"gr1": {file1.Name(), file2.Name()},
|
||||
"gr2": {"file://" + file3.Name()},
|
||||
"gr1": {file1.Path, file2.Path},
|
||||
"gr2": {"file://" + file3.Path},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
Expect(sut.groupCaches["gr1"].ElementCount()).Should(Equal(3))
|
||||
|
@ -264,7 +272,8 @@ var _ = Describe("ListCache", func() {
|
|||
"gr1": {file1, file2, file3},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(),
|
||||
defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
Expect(sut.groupCaches["gr1"].ElementCount()).Should(Equal(38000))
|
||||
|
@ -276,7 +285,8 @@ var _ = Describe("ListCache", func() {
|
|||
"gr1": {"inlinedomain1.com\n#some comment\ninlinedomain2.com"},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(),
|
||||
defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
Expect(sut.groupCaches["gr1"].ElementCount()).Should(Equal(2))
|
||||
|
@ -296,7 +306,8 @@ var _ = Describe("ListCache", func() {
|
|||
"gr1": {"inlinedomain1.com\n" + strings.Repeat("longString", 100000)},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(),
|
||||
defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
found, group := sut.Match("inlinedomain1.com", []string{"gr1"})
|
||||
|
@ -310,7 +321,8 @@ var _ = Describe("ListCache", func() {
|
|||
"gr1": {"/^apple\\.(de|com)$/\n"},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, 0, NewDownloader(),
|
||||
defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
found, group := sut.Match("apple.com", []string{"gr1"})
|
||||
|
@ -331,7 +343,8 @@ var _ = Describe("ListCache", func() {
|
|||
"gr2": {"inline\ndefinition\n"},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, time.Hour, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, time.Hour, NewDownloader(),
|
||||
defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
c := sut.Configuration()
|
||||
|
@ -342,10 +355,11 @@ var _ = Describe("ListCache", func() {
|
|||
When("refresh is disabled", func() {
|
||||
It("should print 'refresh disabled'", func() {
|
||||
lists := map[string][]string{
|
||||
"gr1": {emptyFile.Name()},
|
||||
"gr1": {emptyFile.Path},
|
||||
}
|
||||
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, -1, NewDownloader(), defaultProcessingConcurrency)
|
||||
sut, err := NewListCache(ListCacheTypeBlacklist, lists, -1, NewDownloader(),
|
||||
defaultProcessingConcurrency, false)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
c := sut.Configuration()
|
||||
|
@ -353,6 +367,20 @@ var _ = Describe("ListCache", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("StartStrategy", func() {
|
||||
When("async load is enabled", func() {
|
||||
It("should never return an error", func() {
|
||||
lists := map[string][]string{
|
||||
"gr1": {"doesnotexist"},
|
||||
}
|
||||
|
||||
_, err := NewListCache(ListCacheTypeBlacklist, lists, -1, NewDownloader(),
|
||||
defaultProcessingConcurrency, true)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
type MockDownloader struct {
|
||||
|
@ -366,7 +394,7 @@ func (m *MockDownloader) DownloadFile(_ string) (io.ReadCloser, error) {
|
|||
}
|
||||
|
||||
func createTestListFile(dir string, totalLines int) string {
|
||||
file, err := ioutil.TempFile(dir, "blocky")
|
||||
file, err := os.CreateTemp(dir, "blocky")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package log
|
||||
|
||||
//go:generate go-enum -f=$GOFILE --marshal --names
|
||||
//go:generate go run github.com/abice/go-enum -f=$GOFILE --marshal --names
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -69,7 +69,7 @@ func ConfigureLogger(logLevel Level, formatType FormatType, logTimestamp bool) {
|
|||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
FullTimestamp: true,
|
||||
ForceFormatting: true,
|
||||
ForceColors: true,
|
||||
ForceColors: false,
|
||||
QuoteEmptyFields: true,
|
||||
DisableTimestamp: !logTimestamp}
|
||||
|
||||
|
@ -87,5 +87,5 @@ func ConfigureLogger(logLevel Level, formatType FormatType, logTimestamp bool) {
|
|||
|
||||
// Silence disables the logger output
|
||||
func Silence() {
|
||||
logger.Out = ioutil.Discard
|
||||
logger.Out = io.Discard
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
FormatTypeJson
|
||||
)
|
||||
|
||||
var ErrInvalidFormatType = fmt.Errorf("not a valid FormatType, try [%s]", strings.Join(_FormatTypeNames, ", "))
|
||||
|
||||
const _FormatTypeName = "textjson"
|
||||
|
||||
var _FormatTypeNames = []string{
|
||||
|
@ -57,7 +59,7 @@ func ParseFormatType(name string) (FormatType, error) {
|
|||
if x, ok := _FormatTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return FormatType(0), fmt.Errorf("%s is not a valid FormatType, try [%s]", name, strings.Join(_FormatTypeNames, ", "))
|
||||
return FormatType(0), fmt.Errorf("%s is %w", name, ErrInvalidFormatType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -91,6 +93,8 @@ const (
|
|||
LevelFatal
|
||||
)
|
||||
|
||||
var ErrInvalidLevel = fmt.Errorf("not a valid Level, try [%s]", strings.Join(_LevelNames, ", "))
|
||||
|
||||
const _LevelName = "infotracedebugwarnerrorfatal"
|
||||
|
||||
var _LevelNames = []string{
|
||||
|
@ -140,7 +144,7 @@ func ParseLevel(name string) (Level, error) {
|
|||
if x, ok := _LevelValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return Level(0), fmt.Errorf("%s is not a valid Level, try [%s]", name, strings.Join(_LevelNames, ", "))
|
||||
return Level(0), fmt.Errorf("%s is %w", name, ErrInvalidLevel)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
|
||||
reaper "github.com/ramr/go-reaper"
|
||||
)
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
go reaper.Reap()
|
||||
|
||||
setLocaltime()
|
||||
}
|
||||
|
||||
// set localtime to /etc/localtime if available
|
||||
// or modify the system time with the TZ environment variable if it is provided
|
||||
func setLocaltime() {
|
||||
// load /etc/localtime without modifying it
|
||||
if lt, err := os.ReadFile("/etc/localtime"); err == nil {
|
||||
if t, err := time.LoadLocationFromTZData("", lt); err == nil {
|
||||
time.Local = t
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// use zoneinfo from time/tzdata and set location with the TZ environment variable
|
||||
if tz := os.Getenv("TZ"); tz != "" {
|
||||
if t, err := time.LoadLocation(tz); err == nil {
|
||||
time.Local = t
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package model
|
||||
|
||||
//go:generate go-enum -f=$GOFILE --marshal --names
|
||||
//go:generate go run github.com/abice/go-enum -f=$GOFILE --marshal --names
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
@ -17,6 +17,8 @@ import (
|
|||
// CUSTOMDNS // the query was resolved by a custom rule
|
||||
// HOSTSFILE // the query was resolved by looking up the hosts file
|
||||
// FILTERED // the query was filtered by query type
|
||||
// NOTFQDN // the query was filtered as it is not fqdn conform
|
||||
// SPECIAL // the query was resolved by the special use domain name resolver
|
||||
// )
|
||||
type ResponseType int
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
RequestProtocolUDP
|
||||
)
|
||||
|
||||
var ErrInvalidRequestProtocol = fmt.Errorf("not a valid RequestProtocol, try [%s]", strings.Join(_RequestProtocolNames, ", "))
|
||||
|
||||
const _RequestProtocolName = "TCPUDP"
|
||||
|
||||
var _RequestProtocolNames = []string{
|
||||
|
@ -57,7 +59,7 @@ func ParseRequestProtocol(name string) (RequestProtocol, error) {
|
|||
if x, ok := _RequestProtocolValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return RequestProtocol(0), fmt.Errorf("%s is not a valid RequestProtocol, try [%s]", name, strings.Join(_RequestProtocolNames, ", "))
|
||||
return RequestProtocol(0), fmt.Errorf("%s is %w", name, ErrInvalidRequestProtocol)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
@ -98,9 +100,17 @@ const (
|
|||
// ResponseTypeFILTERED is a ResponseType of type FILTERED.
|
||||
// the query was filtered by query type
|
||||
ResponseTypeFILTERED
|
||||
// ResponseTypeNOTFQDN is a ResponseType of type NOTFQDN.
|
||||
// the query was filtered as it is not fqdn conform
|
||||
ResponseTypeNOTFQDN
|
||||
// ResponseTypeSPECIAL is a ResponseType of type SPECIAL.
|
||||
// the query was resolved by the special use domain name resolver
|
||||
ResponseTypeSPECIAL
|
||||
)
|
||||
|
||||
const _ResponseTypeName = "RESOLVEDCACHEDBLOCKEDCONDITIONALCUSTOMDNSHOSTSFILEFILTERED"
|
||||
var ErrInvalidResponseType = fmt.Errorf("not a valid ResponseType, try [%s]", strings.Join(_ResponseTypeNames, ", "))
|
||||
|
||||
const _ResponseTypeName = "RESOLVEDCACHEDBLOCKEDCONDITIONALCUSTOMDNSHOSTSFILEFILTEREDNOTFQDNSPECIAL"
|
||||
|
||||
var _ResponseTypeNames = []string{
|
||||
_ResponseTypeName[0:8],
|
||||
|
@ -110,6 +120,8 @@ var _ResponseTypeNames = []string{
|
|||
_ResponseTypeName[32:41],
|
||||
_ResponseTypeName[41:50],
|
||||
_ResponseTypeName[50:58],
|
||||
_ResponseTypeName[58:65],
|
||||
_ResponseTypeName[65:72],
|
||||
}
|
||||
|
||||
// ResponseTypeNames returns a list of possible string values of ResponseType.
|
||||
|
@ -127,6 +139,8 @@ var _ResponseTypeMap = map[ResponseType]string{
|
|||
ResponseTypeCUSTOMDNS: _ResponseTypeName[32:41],
|
||||
ResponseTypeHOSTSFILE: _ResponseTypeName[41:50],
|
||||
ResponseTypeFILTERED: _ResponseTypeName[50:58],
|
||||
ResponseTypeNOTFQDN: _ResponseTypeName[58:65],
|
||||
ResponseTypeSPECIAL: _ResponseTypeName[65:72],
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
|
@ -145,6 +159,8 @@ var _ResponseTypeValue = map[string]ResponseType{
|
|||
_ResponseTypeName[32:41]: ResponseTypeCUSTOMDNS,
|
||||
_ResponseTypeName[41:50]: ResponseTypeHOSTSFILE,
|
||||
_ResponseTypeName[50:58]: ResponseTypeFILTERED,
|
||||
_ResponseTypeName[58:65]: ResponseTypeNOTFQDN,
|
||||
_ResponseTypeName[65:72]: ResponseTypeSPECIAL,
|
||||
}
|
||||
|
||||
// ParseResponseType attempts to convert a string to a ResponseType.
|
||||
|
@ -152,7 +168,7 @@ func ParseResponseType(name string) (ResponseType, error) {
|
|||
if x, ok := _ResponseTypeValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return ResponseType(0), fmt.Errorf("%s is not a valid ResponseType, try [%s]", name, strings.Join(_ResponseTypeNames, ", "))
|
||||
return ResponseType(0), fmt.Errorf("%s is %w", name, ErrInvalidResponseType)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
|
|
|
@ -2,6 +2,7 @@ package querylog
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -70,7 +71,7 @@ func newDatabaseWriter(target gorm.Dialector, logRetentionDays uint64,
|
|||
}
|
||||
|
||||
// Migrate the schema
|
||||
if err := db.AutoMigrate(&logEntry{}); err != nil {
|
||||
if err := databaseMigration(db); err != nil {
|
||||
return nil, fmt.Errorf("can't perform auto migration: %w", err)
|
||||
}
|
||||
|
||||
|
@ -84,6 +85,35 @@ func newDatabaseWriter(target gorm.Dialector, logRetentionDays uint64,
|
|||
return w, nil
|
||||
}
|
||||
|
||||
func databaseMigration(db *gorm.DB) error {
|
||||
if err := db.AutoMigrate(&logEntry{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tableName := db.NamingStrategy.TableName(reflect.TypeOf(logEntry{}).Name())
|
||||
|
||||
// create unmapped primary key
|
||||
switch db.Config.Name() {
|
||||
case "mysql":
|
||||
tx := db.Exec("ALTER TABLE `" + tableName + "` ADD `id` INT PRIMARY KEY AUTO_INCREMENT")
|
||||
if tx.Error != nil {
|
||||
// mysql doesn't support "add column if not exist"
|
||||
if strings.Contains(tx.Error.Error(), "1060") {
|
||||
// error 1060: duplicate column name
|
||||
// ignore it
|
||||
return nil
|
||||
}
|
||||
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
case "postgres":
|
||||
return db.Exec("ALTER TABLE " + tableName + " ADD column if not exists id serial primary key").Error
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DatabaseWriter) periodicFlush() {
|
||||
ticker := time.NewTicker(d.dbFlushPeriod)
|
||||
defer ticker.Stop()
|
||||
|
|
|
@ -9,61 +9,102 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
var _ = Describe("DatabaseWriter", func() {
|
||||
|
||||
Describe("Database query log", func() {
|
||||
When("New log entry was created", func() {
|
||||
It("should be persisted in the database", func() {
|
||||
sqlite := sqlite.Open("file::memory:")
|
||||
writer, err := newDatabaseWriter(sqlite, 7, time.Millisecond)
|
||||
Expect(err).Should(Succeed())
|
||||
request := &model.Request{
|
||||
Req: util.NewMsgWithQuestion("google.de.", dns.Type(dns.TypeA)),
|
||||
Log: logrus.NewEntry(log.Log()),
|
||||
}
|
||||
res, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
Describe("Database query log to sqlite", func() {
|
||||
var (
|
||||
sqliteDB gorm.Dialector
|
||||
writer *DatabaseWriter
|
||||
request *model.Request
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
sqliteDB = sqlite.Open("file::memory:")
|
||||
|
||||
request = &model.Request{
|
||||
Req: util.NewMsgWithQuestion("google.de.", dns.Type(dns.TypeA)),
|
||||
Log: logrus.NewEntry(log.Log()),
|
||||
}
|
||||
})
|
||||
|
||||
When("New log entry was created", func() {
|
||||
BeforeEach(func() {
|
||||
writer, err = newDatabaseWriter(sqliteDB, 7, time.Millisecond)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
|
||||
It("should be persisted in the database", func() {
|
||||
res, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
response := &model.Response{
|
||||
Res: res,
|
||||
Reason: "Resolved",
|
||||
RType: model.ResponseTypeRESOLVED,
|
||||
}
|
||||
|
||||
// one entry with now as timestamp
|
||||
writer.Write(&LogEntry{
|
||||
Request: request,
|
||||
Response: response,
|
||||
Start: time.Now(),
|
||||
DurationMs: 20,
|
||||
})
|
||||
|
||||
// one entry before 2 days
|
||||
writer.Write(&LogEntry{
|
||||
Request: request,
|
||||
Response: response,
|
||||
Start: time.Now().AddDate(0, 0, -2),
|
||||
DurationMs: 20,
|
||||
})
|
||||
|
||||
// force write
|
||||
writer.doDBWrite()
|
||||
|
||||
// 2 entries in the database
|
||||
Eventually(func() int64 {
|
||||
var res int64
|
||||
result := writer.db.Find(&logEntry{})
|
||||
|
||||
result.Count(&res)
|
||||
|
||||
return res
|
||||
}, "5s").Should(BeNumerically("==", 2))
|
||||
|
||||
// do cleanup now
|
||||
writer.CleanUp()
|
||||
|
||||
// now only 1 entry in the database
|
||||
Eventually(func() (res int64) {
|
||||
result := writer.db.Find(&logEntry{})
|
||||
|
||||
result.Count(&res)
|
||||
|
||||
return res
|
||||
}, "1s").Should(BeNumerically("==", 1))
|
||||
}, "5s").Should(BeNumerically("==", 2))
|
||||
})
|
||||
})
|
||||
|
||||
When("There are log entries with timestamp exceeding the retention period", func() {
|
||||
BeforeEach(func() {
|
||||
writer, err = newDatabaseWriter(sqliteDB, 1, time.Millisecond)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
|
||||
It("these old entries should be deleted", func() {
|
||||
sqlite := sqlite.Open("file::memory:")
|
||||
writer, err := newDatabaseWriter(sqlite, 1, time.Millisecond)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
request := &model.Request{
|
||||
Req: util.NewMsgWithQuestion("google.de.", dns.Type(dns.TypeA)),
|
||||
Log: logrus.NewEntry(log.Log()),
|
||||
}
|
||||
res, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
response := &model.Response{
|
||||
Res: res,
|
||||
Reason: "Resolved",
|
||||
|
@ -86,6 +127,9 @@ var _ = Describe("DatabaseWriter", func() {
|
|||
DurationMs: 20,
|
||||
})
|
||||
|
||||
// force write
|
||||
writer.doDBWrite()
|
||||
|
||||
// 2 entries in the database
|
||||
Eventually(func() int64 {
|
||||
var res int64
|
||||
|
@ -94,7 +138,7 @@ var _ = Describe("DatabaseWriter", func() {
|
|||
result.Count(&res)
|
||||
|
||||
return res
|
||||
}, "1s").Should(BeNumerically("==", 2))
|
||||
}, "5s").Should(BeNumerically("==", 2))
|
||||
|
||||
// do cleanup now
|
||||
writer.CleanUp()
|
||||
|
@ -106,10 +150,12 @@ var _ = Describe("DatabaseWriter", func() {
|
|||
result.Count(&res)
|
||||
|
||||
return res
|
||||
}, "1s").Should(BeNumerically("==", 1))
|
||||
}, "5s").Should(BeNumerically("==", 1))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Database query log fails", func() {
|
||||
When("mysql connection parameters wrong", func() {
|
||||
It("should be log with fatal", func() {
|
||||
_, err := NewDatabaseWriter("mysql", "wrong param", 7, 1)
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -59,14 +58,13 @@ func (d *FileWriter) Write(entry *LogEntry) {
|
|||
"can't create/open file", err)
|
||||
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
writer := createCsvWriter(file)
|
||||
|
||||
err := writer.Write(createQueryLogRow(entry))
|
||||
util.LogOnErrorWithEntry(log.PrefixedLog(loggerPrefixFileWriter).WithField("file_name", writePath),
|
||||
"can't write to file", err)
|
||||
writer.Flush()
|
||||
|
||||
_ = file.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +76,7 @@ func (d *FileWriter) CleanUp() {
|
|||
|
||||
logger.Trace("starting clean up")
|
||||
|
||||
files, err := ioutil.ReadDir(d.target)
|
||||
files, err := os.ReadDir(d.target)
|
||||
|
||||
util.LogOnErrorWithEntry(logger.WithField("target", d.target), "can't list log directory: ", err)
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/0xERR0R/blocky/helpertest"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
|
@ -21,14 +20,16 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("FileWriter", func() {
|
||||
var tmpDir string
|
||||
var err error
|
||||
BeforeEach(func() {
|
||||
tmpDir, err = ioutil.TempDir("", "fileWriter")
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
AfterEach(func() {
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
var (
|
||||
tmpDir *helpertest.TmpFolder
|
||||
err error
|
||||
writer *FileWriter
|
||||
)
|
||||
|
||||
JustBeforeEach(func() {
|
||||
tmpDir = helpertest.NewTmpFolder("fileWriter")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
})
|
||||
|
||||
Describe("CSV writer", func() {
|
||||
|
@ -40,9 +41,10 @@ var _ = Describe("FileWriter", func() {
|
|||
})
|
||||
When("New log entry was created", func() {
|
||||
It("should be logged in one file", func() {
|
||||
tmpDir, err = ioutil.TempDir("", "queryLoggingResolver")
|
||||
writer, err = NewCSVWriter(tmpDir.Path, false, 0)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
writer, _ := NewCSVWriter(tmpDir, false, 0)
|
||||
|
||||
res, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -81,15 +83,17 @@ var _ = Describe("FileWriter", func() {
|
|||
})
|
||||
})
|
||||
|
||||
csvLines := readCsv(filepath.Join(tmpDir, fmt.Sprintf("%s_ALL.log", time.Now().Format("2006-01-02"))))
|
||||
Expect(csvLines).Should(HaveLen(2))
|
||||
|
||||
Eventually(func(g Gomega) int {
|
||||
return len(readCsv(tmpDir.JoinPath(
|
||||
fmt.Sprintf("%s_ALL.log", time.Now().Format("2006-01-02")))))
|
||||
}).Should(Equal(2))
|
||||
})
|
||||
|
||||
It("should be logged in separate files per client", func() {
|
||||
tmpDir, err = ioutil.TempDir("", "queryLoggingResolver")
|
||||
writer, err = NewCSVWriter(tmpDir.Path, true, 0)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
writer, _ := NewCSVWriter(tmpDir, true, 0)
|
||||
|
||||
res, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -128,19 +132,24 @@ var _ = Describe("FileWriter", func() {
|
|||
})
|
||||
})
|
||||
|
||||
csvLines := readCsv(filepath.Join(tmpDir, fmt.Sprintf("%s_client1.log", time.Now().Format("2006-01-02"))))
|
||||
Expect(csvLines).Should(HaveLen(1))
|
||||
Eventually(func(g Gomega) int {
|
||||
return len(readCsv(tmpDir.JoinPath(
|
||||
fmt.Sprintf("%s_client1.log", time.Now().Format("2006-01-02")))))
|
||||
}).Should(Equal(1))
|
||||
|
||||
csvLines = readCsv(filepath.Join(tmpDir, fmt.Sprintf("%s_client2.log", time.Now().Format("2006-01-02"))))
|
||||
Expect(csvLines).Should(HaveLen(1))
|
||||
Eventually(func(g Gomega) int {
|
||||
return len(readCsv(tmpDir.JoinPath(
|
||||
fmt.Sprintf("%s_client2.log", time.Now().Format("2006-01-02")))))
|
||||
}).Should(Equal(1))
|
||||
|
||||
})
|
||||
})
|
||||
When("Cleanup is called", func() {
|
||||
It("should delete old files", func() {
|
||||
tmpDir, err = ioutil.TempDir("", "queryLoggingResolver")
|
||||
writer, err = NewCSVWriter(tmpDir.Path, false, 1)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
writer, _ := NewCSVWriter(tmpDir, false, 1)
|
||||
|
||||
res, err := util.NewMsgWithAnswer("example.com", 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -173,19 +182,27 @@ var _ = Describe("FileWriter", func() {
|
|||
Reason: "Resolved",
|
||||
RType: model.ResponseTypeRESOLVED,
|
||||
},
|
||||
Start: time.Now().AddDate(0, 0, -2),
|
||||
Start: time.Now().AddDate(0, 0, -3),
|
||||
DurationMs: 20,
|
||||
})
|
||||
})
|
||||
fmt.Println(tmpDir.Path)
|
||||
|
||||
files, err := ioutil.ReadDir(tmpDir)
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(files).Should(HaveLen(2))
|
||||
writer.CleanUp()
|
||||
Eventually(func(g Gomega) int {
|
||||
filesCount, err := tmpDir.CountFiles()
|
||||
g.Expect(err).Should(Succeed())
|
||||
|
||||
files, err = ioutil.ReadDir(tmpDir)
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(files).Should(HaveLen(1))
|
||||
return filesCount
|
||||
}, "20s", "1s").Should(Equal(2))
|
||||
|
||||
go writer.CleanUp()
|
||||
|
||||
Eventually(func(g Gomega) int {
|
||||
filesCount, err := tmpDir.CountFiles()
|
||||
g.Expect(err).Should(Succeed())
|
||||
|
||||
return filesCount
|
||||
}, "20s", "1s").Should(Equal(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -101,13 +101,15 @@ func NewBlockingResolver(cfg config.BlockingConfig,
|
|||
refreshPeriod := time.Duration(cfg.RefreshPeriod)
|
||||
downloader := createDownloader(cfg, bootstrap)
|
||||
blacklistMatcher, blErr := lists.NewListCache(lists.ListCacheTypeBlacklist, cfg.BlackLists,
|
||||
refreshPeriod, downloader, cfg.ProcessingConcurrency)
|
||||
refreshPeriod, downloader, cfg.ProcessingConcurrency,
|
||||
(cfg.StartStrategy == config.StartStrategyTypeFast))
|
||||
whitelistMatcher, wlErr := lists.NewListCache(lists.ListCacheTypeWhitelist, cfg.WhiteLists,
|
||||
refreshPeriod, downloader, cfg.ProcessingConcurrency)
|
||||
refreshPeriod, downloader, cfg.ProcessingConcurrency,
|
||||
(cfg.StartStrategy == config.StartStrategyTypeFast))
|
||||
whitelistOnlyGroups := determineWhitelistOnlyGroups(&cfg)
|
||||
|
||||
err = multierror.Append(err, blErr, wlErr).ErrorOrNil()
|
||||
if err != nil && cfg.FailStartOnListError {
|
||||
if err != nil && cfg.StartStrategy == config.StartStrategyTypeFailOnError {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -465,6 +467,9 @@ func (r *BlockingResolver) isGroupDisabled(group string) bool {
|
|||
|
||||
// returns groups which should be checked for client's request
|
||||
func (r *BlockingResolver) groupsToCheckForClient(request *model.Request) []string {
|
||||
r.status.lock.RLock()
|
||||
defer r.status.lock.RUnlock()
|
||||
|
||||
var groups []string
|
||||
// try client names
|
||||
for _, cName := range request.ClientNames {
|
||||
|
@ -613,6 +618,9 @@ func (r *BlockingResolver) queryForFQIdentifierIPs(identifier string) (result []
|
|||
}
|
||||
|
||||
func (r *BlockingResolver) initFQDNIPCache() {
|
||||
r.status.lock.Lock()
|
||||
defer r.status.lock.Unlock()
|
||||
|
||||
identifiers := make([]string, 0)
|
||||
|
||||
for identifier := range r.clientGroupsBlock {
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/creasty/defaults"
|
||||
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
@ -20,22 +19,26 @@ import (
|
|||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var group1File, group2File, defaultGroupFile *os.File
|
||||
var group1File, group2File, defaultGroupFile *TmpFile
|
||||
var tmpDir *TmpFolder
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
group1File = TempFile("DOMAIN1.com")
|
||||
group2File = TempFile("blocked2.com")
|
||||
defaultGroupFile = TempFile(
|
||||
`blocked3.com
|
||||
123.145.123.145
|
||||
2001:db8:85a3:08d3::370:7344
|
||||
badcnamedomain.com`)
|
||||
})
|
||||
tmpDir = NewTmpFolder("BlockingResolver")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
_ = group1File.Close()
|
||||
_ = group2File.Close()
|
||||
_ = defaultGroupFile.Close()
|
||||
group1File = tmpDir.CreateStringFile("group1File", "DOMAIN1.com")
|
||||
Expect(group1File.Error).Should(Succeed())
|
||||
|
||||
group2File = tmpDir.CreateStringFile("group2File", "blocked2.com")
|
||||
Expect(group2File.Error).Should(Succeed())
|
||||
|
||||
defaultGroupFile = tmpDir.CreateStringFile("defaultGroupFile",
|
||||
"blocked3.com",
|
||||
"123.145.123.145",
|
||||
"2001:db8:85a3:08d3::370:7344",
|
||||
"badcnamedomain.com")
|
||||
Expect(defaultGroupFile.Error).Should(Succeed())
|
||||
})
|
||||
|
||||
var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
||||
|
@ -85,8 +88,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]string{
|
||||
"gr1": {group1File.Name()},
|
||||
"gr2": {group2File.Name()},
|
||||
"gr1": {group1File.Path},
|
||||
"gr2": {group2File.Path},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -114,8 +117,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]string{
|
||||
"gr1": {group1File.Name()},
|
||||
"gr2": {group2File.Name()},
|
||||
"gr1": {group1File.Path},
|
||||
"gr2": {group2File.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
|
@ -137,13 +140,12 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
return nil
|
||||
}
|
||||
Bus().Publish(ApplicationStarted, "")
|
||||
time.Sleep(time.Second)
|
||||
Eventually(func(g Gomega) {
|
||||
resp, err = sut.Resolve(newRequestWithClient("blocked2.com.", dns.Type(dns.TypeA), "192.168.178.39", "client1"))
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
g.Expect(resp.Res.Answer).ShouldNot(BeNil())
|
||||
g.Expect(resp.Res.Answer).Should(BeDNSRecord("blocked2.com.", dns.TypeA, 60, "0.0.0.0"))
|
||||
}, "1s").Should(Succeed())
|
||||
}, "10s", "1s").Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -154,9 +156,9 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockTTL: config.Duration(6 * time.Hour),
|
||||
BlackLists: map[string][]string{
|
||||
"gr1": {group1File.Name()},
|
||||
"gr2": {group2File.Name()},
|
||||
"defaultGroup": {defaultGroupFile.Name()},
|
||||
"gr1": {group1File.Path},
|
||||
"gr2": {group2File.Path},
|
||||
"defaultGroup": {defaultGroupFile.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"client1": {"gr1"},
|
||||
|
@ -293,7 +295,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]string{
|
||||
"defaultGroup": {defaultGroupFile.Name()},
|
||||
"defaultGroup": {defaultGroupFile.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"defaultGroup"},
|
||||
|
@ -318,7 +320,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockType: "ZEROIP",
|
||||
BlackLists: map[string][]string{
|
||||
"defaultGroup": {defaultGroupFile.Name()},
|
||||
"defaultGroup": {defaultGroupFile.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"defaultGroup"},
|
||||
|
@ -353,7 +355,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockTTL: config.Duration(6 * time.Hour),
|
||||
BlackLists: map[string][]string{
|
||||
"defaultGroup": {defaultGroupFile.Name()},
|
||||
"defaultGroup": {defaultGroupFile.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"defaultGroup"},
|
||||
|
@ -381,7 +383,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BeforeEach(func() {
|
||||
sutConfig = config.BlockingConfig{
|
||||
BlackLists: map[string][]string{
|
||||
"defaultGroup": {defaultGroupFile.Name()},
|
||||
"defaultGroup": {defaultGroupFile.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"defaultGroup"},
|
||||
|
@ -451,8 +453,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Name()}},
|
||||
WhiteLists: map[string][]string{"gr1": {group1File.Name()}},
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Path}},
|
||||
WhiteLists: map[string][]string{"gr1": {group1File.Path}},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
},
|
||||
|
@ -472,8 +474,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BlockType: "zeroIP",
|
||||
BlockTTL: config.Duration(60 * time.Second),
|
||||
WhiteLists: map[string][]string{
|
||||
"gr1": {group1File.Name()},
|
||||
"gr2": {group2File.Name()},
|
||||
"gr1": {group1File.Path},
|
||||
"gr2": {group2File.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
|
@ -533,8 +535,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Name()}},
|
||||
WhiteLists: map[string][]string{"gr1": {defaultGroupFile.Name()}},
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Path}},
|
||||
WhiteLists: map[string][]string{"gr1": {defaultGroupFile.Path}},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
},
|
||||
|
@ -555,7 +557,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Name()}},
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Path}},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
},
|
||||
|
@ -590,8 +592,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BeforeEach(func() {
|
||||
sutConfig = config.BlockingConfig{
|
||||
BlackLists: map[string][]string{
|
||||
"defaultGroup": {defaultGroupFile.Name()},
|
||||
"group1": {group1File.Name()},
|
||||
"defaultGroup": {defaultGroupFile.Path},
|
||||
"group1": {group1File.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"defaultGroup", "group1"},
|
||||
|
@ -821,7 +823,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.BlockingConfig{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Name()}},
|
||||
BlackLists: map[string][]string{"gr1": {group1File.Path}},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
},
|
||||
|
@ -856,14 +858,13 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
MatchError("unknown blockType 'wrong', please use one of: ZeroIP, NxDomain or specify destination IP address(es)"))
|
||||
})
|
||||
})
|
||||
When("failStartOnListError is active", func() {
|
||||
|
||||
When("startStrategy is failOnError", func() {
|
||||
It("should fail if lists can't be downloaded", func() {
|
||||
_, err := NewBlockingResolver(config.BlockingConfig{
|
||||
BlackLists: map[string][]string{"gr1": {"wrongPath"}},
|
||||
WhiteLists: map[string][]string{"whitelist": {"wrongPath"}},
|
||||
FailStartOnListError: true,
|
||||
BlockType: "zeroIp",
|
||||
BlackLists: map[string][]string{"gr1": {"wrongPath"}},
|
||||
WhiteLists: map[string][]string{"whitelist": {"wrongPath"}},
|
||||
StartStrategy: config.StartStrategyTypeFailOnError,
|
||||
BlockType: "zeroIp",
|
||||
}, nil, skipUpstreamCheck)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
|
|
|
@ -26,7 +26,8 @@ var (
|
|||
|
||||
// Bootstrap allows resolving hostnames using the configured bootstrap DNS.
|
||||
type Bootstrap struct {
|
||||
log *logrus.Entry
|
||||
log *logrus.Entry
|
||||
startVerifyUpstream bool
|
||||
|
||||
resolver Resolver
|
||||
upstream Resolver // the upstream that's part of the above resolver
|
||||
|
@ -64,9 +65,10 @@ func NewBootstrap(cfg *config.Config) (b *Bootstrap, err error) {
|
|||
// This also prevents the GC to clean up these two structs, but is not currently an
|
||||
// issue since they stay allocated until the process terminates
|
||||
b = &Bootstrap{
|
||||
log: log,
|
||||
upstreamIPs: ips,
|
||||
systemResolver: net.DefaultResolver, // allow replacing it during tests
|
||||
log: log,
|
||||
upstreamIPs: ips,
|
||||
systemResolver: net.DefaultResolver, // allow replacing it during tests
|
||||
startVerifyUpstream: cfg.StartVerifyUpstream,
|
||||
}
|
||||
|
||||
if upstream.IsDefault() {
|
||||
|
@ -96,26 +98,18 @@ func (b *Bootstrap) UpstreamIPs(r *UpstreamResolver) (*IPSet, error) {
|
|||
func (b *Bootstrap) resolveUpstream(r Resolver, host string) ([]net.IP, error) {
|
||||
// Use system resolver if no bootstrap is configured
|
||||
if b.resolver == nil {
|
||||
filteredQTypes := config.GetConfig().Filtering.QueryTypes
|
||||
|
||||
network := "ip"
|
||||
if filteredQTypes.Contains(dns.Type(dns.TypeAAAA)) {
|
||||
network = "ip4"
|
||||
} else if filteredQTypes.Contains(dns.Type(dns.TypeA)) {
|
||||
network = "ip6"
|
||||
}
|
||||
|
||||
cfg := config.GetConfig()
|
||||
ctx := context.Background()
|
||||
|
||||
timeout := config.GetConfig().UpstreamTimeout
|
||||
timeout := cfg.UpstreamTimeout
|
||||
if timeout != 0 {
|
||||
var cancel context.CancelFunc
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(timeout))
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout))
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
return b.systemResolver.LookupIP(ctx, network, host)
|
||||
return b.systemResolver.LookupIP(ctx, cfg.ConnectIPVersion.Net(), host)
|
||||
}
|
||||
|
||||
if r == b.upstream {
|
||||
|
@ -145,14 +139,16 @@ func (b *Bootstrap) NewHTTPTransport() *http.Transport {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
filteredQTypes := config.GetConfig().Filtering.QueryTypes
|
||||
connectIPVersion := config.GetConfig().ConnectIPVersion
|
||||
|
||||
var qTypes []dns.Type
|
||||
|
||||
switch {
|
||||
case strings.HasSuffix(network, "4") || filteredQTypes.Contains(dns.Type(dns.TypeAAAA)):
|
||||
case connectIPVersion != config.IPVersionDual: // ignore `network` if a specific version is configured
|
||||
qTypes = connectIPVersion.QTypes()
|
||||
case strings.HasSuffix(network, "4"):
|
||||
qTypes = []dns.Type{dns.Type(dns.TypeA)}
|
||||
case strings.HasSuffix(network, "6") || filteredQTypes.Contains(dns.Type(dns.TypeA)):
|
||||
case strings.HasSuffix(network, "6"):
|
||||
qTypes = []dns.Type{dns.Type(dns.TypeAAAA)}
|
||||
default:
|
||||
qTypes = v4v6QTypes
|
||||
|
|
|
@ -112,7 +112,7 @@ func (r *CachingResolver) onExpired(cacheKey string) (val interface{}, ttl time.
|
|||
if response.Res.Rcode == dns.RcodeSuccess {
|
||||
evt.Bus().Publish(evt.CachingDomainPrefetched, domainName)
|
||||
|
||||
return cacheValue{response.Res.Answer, true}, time.Duration(r.adjustTTLs(response.Res.Answer)) * time.Second
|
||||
return cacheValue{response.Res.Answer, true}, r.adjustTTLs(response.Res.Answer)
|
||||
}
|
||||
} else {
|
||||
util.LogOnError(fmt.Sprintf("can't prefetch '%s' ", domainName), err)
|
||||
|
@ -232,7 +232,7 @@ func (r *CachingResolver) putInCache(cacheKey string, response *model.Response,
|
|||
|
||||
if response.Res.Rcode == dns.RcodeSuccess {
|
||||
// put value into cache
|
||||
r.resultCache.Put(cacheKey, cacheValue{answer, prefetch}, time.Duration(r.adjustTTLs(answer))*time.Second)
|
||||
r.resultCache.Put(cacheKey, cacheValue{answer, prefetch}, r.adjustTTLs(answer))
|
||||
} else if response.Res.Rcode == dns.RcodeNameError {
|
||||
if r.cacheTimeNegative > 0 {
|
||||
// put return code if NXDOMAIN
|
||||
|
@ -249,7 +249,16 @@ func (r *CachingResolver) putInCache(cacheKey string, response *model.Response,
|
|||
}
|
||||
}
|
||||
|
||||
func (r *CachingResolver) adjustTTLs(answer []dns.RR) (maxTTL uint32) {
|
||||
// adjustTTLs calculates and returns the max TTL (considers also the min and max cache time)
|
||||
// for all records from answer or a negative cache time for empty answer
|
||||
// adjust the TTL in the answer header accordingly
|
||||
func (r *CachingResolver) adjustTTLs(answer []dns.RR) (maxTTL time.Duration) {
|
||||
var max uint32
|
||||
|
||||
if len(answer) == 0 {
|
||||
return r.cacheTimeNegative
|
||||
}
|
||||
|
||||
for _, a := range answer {
|
||||
// if TTL < mitTTL -> adjust the value, set minTTL
|
||||
if r.minCacheTimeSec > 0 {
|
||||
|
@ -264,10 +273,10 @@ func (r *CachingResolver) adjustTTLs(answer []dns.RR) (maxTTL uint32) {
|
|||
}
|
||||
}
|
||||
|
||||
if maxTTL < a.Header().Ttl {
|
||||
maxTTL = a.Header().Ttl
|
||||
if max < a.Header().Ttl {
|
||||
max = a.Header().Ttl
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return time.Duration(max) * time.Second
|
||||
}
|
||||
|
|
|
@ -355,66 +355,98 @@ var _ = Describe("CachingResolver", func() {
|
|||
})
|
||||
|
||||
Describe("Negative cache (caching if upstream resolver returns NXDOMAIN)", func() {
|
||||
When("Upstream resolver returns NXDOMAIN with caching", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer.Rcode = dns.RcodeNameError
|
||||
})
|
||||
|
||||
It("response should be cached", func() {
|
||||
By("first request", func() {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
Context("Caching if upstream resolver returns NXDOMAIN", func() {
|
||||
When("Upstream resolver returns NXDOMAIN with caching", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer.Rcode = dns.RcodeNameError
|
||||
})
|
||||
|
||||
By("second request", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
It("response should be cached", func() {
|
||||
By("first request", func() {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(resp.RType).Should(Equal(ResponseTypeCACHED))
|
||||
g.Expect(resp.Reason).Should(Equal("CACHED NEGATIVE"))
|
||||
g.Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
// still one call to resolver
|
||||
g.Expect(m.Calls).Should(HaveLen(1))
|
||||
}, "500ms").Should(Succeed())
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
|
||||
By("second request", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(resp.RType).Should(Equal(ResponseTypeCACHED))
|
||||
g.Expect(resp.Reason).Should(Equal("CACHED NEGATIVE"))
|
||||
g.Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
// still one call to resolver
|
||||
g.Expect(m.Calls).Should(HaveLen(1))
|
||||
}, "500ms").Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
When("Upstream resolver returns NXDOMAIN without caching", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer.Rcode = dns.RcodeNameError
|
||||
sutConfig = config.CachingConfig{
|
||||
CacheTimeNegative: config.Duration(time.Minute * -1),
|
||||
}
|
||||
})
|
||||
|
||||
It("response shouldn't be cached", func() {
|
||||
By("first request", func() {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
|
||||
By("second request", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
g.Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
g.Expect(m.Calls).Should(HaveLen(2))
|
||||
}, "500ms").Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
When("Upstream resolver returns NXDOMAIN without caching", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer.Rcode = dns.RcodeNameError
|
||||
sutConfig = config.CachingConfig{
|
||||
CacheTimeNegative: config.Duration(time.Minute * -1),
|
||||
}
|
||||
})
|
||||
|
||||
It("response shouldn't be cached", func() {
|
||||
By("first request", func() {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
Context("Caching if upstream resolver returns empty result", func() {
|
||||
When("Upstream resolver returns empty result with caching", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer.Rcode = dns.RcodeSuccess
|
||||
mockAnswer.Answer = make([]dns.RR, 0)
|
||||
})
|
||||
|
||||
By("second request", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
It("response should be cached", func() {
|
||||
By("first request", func() {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
g.Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
g.Expect(m.Calls).Should(HaveLen(2))
|
||||
}, "500ms").Should(Succeed())
|
||||
})
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
|
||||
By("second request", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
resp, err = sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeAAAA)))
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(resp.RType).Should(Equal(ResponseTypeCACHED))
|
||||
g.Expect(resp.Reason).Should(Equal("CACHED"))
|
||||
g.Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
// still one call to resolver
|
||||
g.Expect(m.Calls).Should(HaveLen(1))
|
||||
}, "500ms").Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Not A / AAAA queries should also cached", func() {
|
||||
Describe("Not A / AAAA queries should also be cached", func() {
|
||||
When("MX query will be performed", func() {
|
||||
BeforeEach(func() {
|
||||
mockAnswer, _ = util.NewMsgWithAnswer("google.de.", 180, dns.Type(dns.TypeMX), "10 alt1.aspmx.l.google.com.")
|
||||
|
|
|
@ -129,8 +129,8 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.Res.Answer).Should(HaveLen(2))
|
||||
Expect(resp.Res.Answer).Should(ContainElements(
|
||||
BeDNSRecord("multiple.ips.", dns.TypeA, TTL, "192.168.143.123")),
|
||||
BeDNSRecord("multiple.ips.", dns.TypeA, TTL, "192.168.143.125"))
|
||||
BeDNSRecord("multiple.ips.", dns.TypeA, TTL, "192.168.143.123"),
|
||||
BeDNSRecord("multiple.ips.", dns.TypeA, TTL, "192.168.143.125")))
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
|
@ -157,9 +157,9 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
Expect(resp.Res.Answer).Should(HaveLen(2))
|
||||
Expect(resp.Res.Answer).Should(ContainElements(
|
||||
BeDNSRecord("4.3.3.7.0.7.3.0.e.2.a.8.0.0.0.0.0.0.0.0.3.a.5.8.8.b.d.0.1.0.0.2.ip6.arpa.",
|
||||
dns.TypePTR, TTL, "ip6.domain.")),
|
||||
dns.TypePTR, TTL, "ip6.domain."),
|
||||
BeDNSRecord("4.3.3.7.0.7.3.0.e.2.a.8.0.0.0.0.0.0.0.0.3.a.5.8.8.b.d.0.1.0.0.2.ip6.arpa.",
|
||||
dns.TypePTR, TTL, "multiple.ips."))
|
||||
dns.TypePTR, TTL, "multiple.ips.")))
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type EdeResolver struct {
|
||||
NextResolver
|
||||
config config.EdeConfig
|
||||
}
|
||||
|
||||
func NewEdeResolver(cfg config.EdeConfig) ChainedResolver {
|
||||
return &EdeResolver{
|
||||
config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *EdeResolver) Resolve(request *model.Request) (*model.Response, error) {
|
||||
resp, err := r.next.Resolve(request)
|
||||
|
||||
if r.config.Enable {
|
||||
addExtraReasoning(resp)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (r *EdeResolver) Configuration() (result []string) {
|
||||
if r.config.Enable {
|
||||
result = []string{"activated"}
|
||||
} else {
|
||||
result = []string{"deactivated"}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func addExtraReasoning(res *model.Response) {
|
||||
// dns.ExtendedErrorCodeOther seams broken in some clients
|
||||
infocode := convertToExtendedErrorCode(res.RType)
|
||||
if infocode > 0 {
|
||||
opt := new(dns.OPT)
|
||||
opt.Hdr.Name = "."
|
||||
opt.Hdr.Rrtype = dns.TypeOPT
|
||||
opt.Option = append(opt.Option, convertExtendedError(res, infocode))
|
||||
res.Res.Extra = append(res.Res.Extra, opt)
|
||||
}
|
||||
}
|
||||
|
||||
func convertExtendedError(input *model.Response, infocode uint16) *dns.EDNS0_EDE {
|
||||
return &dns.EDNS0_EDE{
|
||||
InfoCode: infocode,
|
||||
ExtraText: input.Reason,
|
||||
}
|
||||
}
|
||||
|
||||
func convertToExtendedErrorCode(input model.ResponseType) uint16 {
|
||||
switch input {
|
||||
case model.ResponseTypeRESOLVED:
|
||||
return dns.ExtendedErrorCodeOther
|
||||
case model.ResponseTypeCACHED:
|
||||
return dns.ExtendedErrorCodeCachedError
|
||||
case model.ResponseTypeCONDITIONAL:
|
||||
return dns.ExtendedErrorCodeForgedAnswer
|
||||
case model.ResponseTypeCUSTOMDNS:
|
||||
return dns.ExtendedErrorCodeForgedAnswer
|
||||
case model.ResponseTypeHOSTSFILE:
|
||||
return dns.ExtendedErrorCodeForgedAnswer
|
||||
case model.ResponseTypeNOTFQDN:
|
||||
return dns.ExtendedErrorCodeBlocked
|
||||
case model.ResponseTypeBLOCKED:
|
||||
return dns.ExtendedErrorCodeBlocked
|
||||
case model.ResponseTypeFILTERED:
|
||||
return dns.ExtendedErrorCodeFiltered
|
||||
case model.ResponseTypeSPECIAL:
|
||||
return dns.ExtendedErrorCodeFiltered
|
||||
default:
|
||||
return dns.ExtendedErrorCodeOther
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var _ = Describe("EdeResolver", func() {
|
||||
var (
|
||||
sut *EdeResolver
|
||||
sutConfig config.EdeConfig
|
||||
m *MockResolver
|
||||
mockAnswer *dns.Msg
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
mockAnswer = new(dns.Msg)
|
||||
})
|
||||
|
||||
JustBeforeEach(func() {
|
||||
m = &MockResolver{}
|
||||
m.On("Resolve", mock.Anything).Return(&model.Response{
|
||||
Res: mockAnswer,
|
||||
RType: model.ResponseTypeCUSTOMDNS,
|
||||
Reason: "Test",
|
||||
}, nil)
|
||||
|
||||
sut = NewEdeResolver(sutConfig).(*EdeResolver)
|
||||
sut.Next(m)
|
||||
})
|
||||
|
||||
When("Ede is disabled", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.EdeConfig{
|
||||
Enable: false,
|
||||
}
|
||||
})
|
||||
It("Shouldn't add EDE information", func() {
|
||||
resp, err := sut.Resolve(newRequest("example.com", dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.RType).Should(Equal(model.ResponseTypeCUSTOMDNS))
|
||||
Expect(resp.Res.Answer).Should(BeEmpty())
|
||||
Expect(resp.Res.Extra).Should(BeEmpty())
|
||||
|
||||
// delegated to next resolver
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
It("Configure should output deactivated", func() {
|
||||
c := sut.Configuration()
|
||||
Expect(c).Should(HaveLen(1))
|
||||
Expect(c[0]).Should(Equal("deactivated"))
|
||||
})
|
||||
})
|
||||
When("Ede is enabled", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.EdeConfig{
|
||||
Enable: true,
|
||||
}
|
||||
})
|
||||
It("Should add EDE information", func() {
|
||||
resp, err := sut.Resolve(newRequest("example.com", dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.RType).Should(Equal(model.ResponseTypeCUSTOMDNS))
|
||||
Expect(resp.Res.Answer).Should(BeEmpty())
|
||||
Expect(resp.Res.Extra).Should(HaveLen(1))
|
||||
opt, ok := resp.Res.Extra[0].(*dns.OPT)
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(opt).ShouldNot(BeNil())
|
||||
ede, ok := opt.Option[0].(*dns.EDNS0_EDE)
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(ede.InfoCode).Should(Equal(dns.ExtendedErrorCodeForgedAnswer))
|
||||
Expect(ede.ExtraText).Should(Equal("Test"))
|
||||
})
|
||||
It("Configure should output activated", func() {
|
||||
c := sut.Configuration()
|
||||
Expect(c).Should(HaveLen(1))
|
||||
Expect(c[0]).Should(Equal("activated"))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,45 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type FqdnOnlyResolver struct {
|
||||
NextResolver
|
||||
enabled bool
|
||||
}
|
||||
|
||||
func NewFqdnOnlyResolver(cfg config.Config) ChainedResolver {
|
||||
return &FqdnOnlyResolver{
|
||||
enabled: cfg.FqdnOnly,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FqdnOnlyResolver) Resolve(request *model.Request) (*model.Response, error) {
|
||||
if r.enabled {
|
||||
domainFromQuestion := util.ExtractDomain(request.Req.Question[0])
|
||||
if !strings.Contains(domainFromQuestion, ".") {
|
||||
response := new(dns.Msg)
|
||||
response.Rcode = dns.RcodeNameError
|
||||
|
||||
return &model.Response{Res: response, RType: model.ResponseTypeNOTFQDN, Reason: "NOTFQDN"}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return r.next.Resolve(request)
|
||||
}
|
||||
|
||||
func (r *FqdnOnlyResolver) Configuration() (result []string) {
|
||||
if r.enabled {
|
||||
result = []string{"activated"}
|
||||
} else {
|
||||
result = []string{"deactivated"}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
. "github.com/0xERR0R/blocky/model"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var _ = Describe("FqdnOnlyResolver", func() {
|
||||
var (
|
||||
sut *FqdnOnlyResolver
|
||||
sutConfig config.Config
|
||||
m *MockResolver
|
||||
mockAnswer *dns.Msg
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
mockAnswer = new(dns.Msg)
|
||||
})
|
||||
|
||||
JustBeforeEach(func() {
|
||||
sut = NewFqdnOnlyResolver(sutConfig).(*FqdnOnlyResolver)
|
||||
m = &MockResolver{}
|
||||
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer}, nil)
|
||||
sut.Next(m)
|
||||
})
|
||||
|
||||
When("Fqdn only is activated", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Config{
|
||||
FqdnOnly: true,
|
||||
}
|
||||
})
|
||||
It("Should delegate to next resolver if request query is fqdn", func() {
|
||||
resp, err := sut.Resolve(newRequest("example.com", dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Answer).Should(BeEmpty())
|
||||
|
||||
// delegated to next resolver
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
It("Should return NXDOMAIN if request query is not fqdn", func() {
|
||||
resp, err := sut.Resolve(newRequest("example", dns.Type(dns.TypeAAAA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeNOTFQDN))
|
||||
Expect(resp.Res.Answer).Should(BeEmpty())
|
||||
|
||||
// no call of next resolver
|
||||
Expect(m.Calls).Should(BeZero())
|
||||
})
|
||||
It("Configure should output activated", func() {
|
||||
c := sut.Configuration()
|
||||
Expect(c).Should(HaveLen(1))
|
||||
Expect(c[0]).Should(Equal("activated"))
|
||||
})
|
||||
})
|
||||
|
||||
When("Fqdn only is deactivated", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Config{
|
||||
FqdnOnly: false,
|
||||
}
|
||||
})
|
||||
It("Should delegate to next resolver if request query is fqdn", func() {
|
||||
resp, err := sut.Resolve(newRequest("example.com", dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Answer).Should(BeEmpty())
|
||||
|
||||
// delegated to next resolver
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
It("Should delegate to next resolver if request query is not fqdn", func() {
|
||||
resp, err := sut.Resolve(newRequest("example", dns.Type(dns.TypeAAAA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
|
||||
Expect(resp.Res.Answer).Should(BeEmpty())
|
||||
|
||||
// delegated to next resolver
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
It("Configure should output deactivated", func() {
|
||||
c := sut.Configuration()
|
||||
Expect(c).Should(HaveLen(1))
|
||||
Expect(c[0]).Should(Equal("deactivated"))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -14,16 +14,23 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
_, loopback4, _ = net.ParseCIDR("127.0.0.0/8")
|
||||
loopback6 = net.ParseIP("::1")
|
||||
)
|
||||
|
||||
const (
|
||||
hostsFileResolverLogger = "hosts_file_resolver"
|
||||
)
|
||||
|
||||
type HostsFileResolver struct {
|
||||
NextResolver
|
||||
HostsFilePath string
|
||||
hosts []host
|
||||
ttl uint32
|
||||
refreshPeriod time.Duration
|
||||
HostsFilePath string
|
||||
hosts []host
|
||||
ttl uint32
|
||||
refreshPeriod time.Duration
|
||||
filterLoopback bool
|
||||
}
|
||||
|
||||
func (r *HostsFileResolver) handleReverseDNS(request *model.Request) *model.Response {
|
||||
|
@ -119,6 +126,7 @@ func (r *HostsFileResolver) Configuration() (result []string) {
|
|||
result = append(result, fmt.Sprintf("hosts file path: %s", r.HostsFilePath))
|
||||
result = append(result, fmt.Sprintf("hosts TTL: %d", r.ttl))
|
||||
result = append(result, fmt.Sprintf("hosts refresh period: %s", r.refreshPeriod.String()))
|
||||
result = append(result, fmt.Sprintf("filter loopback addresses: %t", r.filterLoopback))
|
||||
} else {
|
||||
result = []string{"deactivated"}
|
||||
}
|
||||
|
@ -128,9 +136,10 @@ func (r *HostsFileResolver) Configuration() (result []string) {
|
|||
|
||||
func NewHostsFileResolver(cfg config.HostsFileConfig) ChainedResolver {
|
||||
r := HostsFileResolver{
|
||||
HostsFilePath: cfg.Filepath,
|
||||
ttl: uint32(time.Duration(cfg.HostsTTL).Seconds()),
|
||||
refreshPeriod: time.Duration(cfg.RefreshPeriod),
|
||||
HostsFilePath: cfg.Filepath,
|
||||
ttl: uint32(time.Duration(cfg.HostsTTL).Seconds()),
|
||||
refreshPeriod: time.Duration(cfg.RefreshPeriod),
|
||||
filterLoopback: cfg.FilterLoopback,
|
||||
}
|
||||
|
||||
if err := r.parseHostsFile(); err != nil {
|
||||
|
@ -150,6 +159,7 @@ type host struct {
|
|||
Aliases []string
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func (r *HostsFileResolver) parseHostsFile() error {
|
||||
const minColumnCount = 2
|
||||
|
||||
|
@ -197,6 +207,11 @@ func (r *HostsFileResolver) parseHostsFile() error {
|
|||
h.IP = net.ParseIP(fields[0])
|
||||
h.Hostname = fields[1]
|
||||
|
||||
// Check if loopback
|
||||
if r.filterLoopback && (loopback4.Contains(h.IP) || loopback6.Equal(h.IP)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(fields) > minColumnCount {
|
||||
for i := 2; i < len(fields); i++ {
|
||||
h.Aliases = append(h.Aliases, fields[i])
|
||||
|
|
|
@ -16,19 +16,29 @@ import (
|
|||
|
||||
var _ = Describe("HostsFileResolver", func() {
|
||||
var (
|
||||
sut *HostsFileResolver
|
||||
m *MockResolver
|
||||
err error
|
||||
resp *Response
|
||||
sut *HostsFileResolver
|
||||
m *MockResolver
|
||||
err error
|
||||
resp *Response
|
||||
tmpDir *TmpFolder
|
||||
tmpFile *TmpFile
|
||||
)
|
||||
|
||||
TTL := uint32(time.Now().Second())
|
||||
|
||||
BeforeEach(func() {
|
||||
tmpDir = NewTmpFolder("HostsFileResolver")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
|
||||
tmpFile = writeHostFile(tmpDir)
|
||||
Expect(tmpFile.Error).Should(Succeed())
|
||||
|
||||
cfg := config.HostsFileConfig{
|
||||
Filepath: "../testdata/hosts.txt",
|
||||
HostsTTL: config.Duration(time.Duration(TTL) * time.Second),
|
||||
RefreshPeriod: config.Duration(30 * time.Minute),
|
||||
Filepath: tmpFile.Path,
|
||||
HostsTTL: config.Duration(time.Duration(TTL) * time.Second),
|
||||
RefreshPeriod: config.Duration(30 * time.Minute),
|
||||
FilterLoopback: true,
|
||||
}
|
||||
sut = NewHostsFileResolver(cfg).(*HostsFileResolver)
|
||||
m = &MockResolver{}
|
||||
|
@ -79,8 +89,8 @@ var _ = Describe("HostsFileResolver", func() {
|
|||
|
||||
When("Hosts file can be located", func() {
|
||||
It("should parse it successfully", func() {
|
||||
Expect(sut).Should(Not(BeNil()))
|
||||
Expect(sut.hosts).Should(HaveLen(7))
|
||||
Expect(sut).ShouldNot(BeNil())
|
||||
Expect(sut.hosts).Should(HaveLen(4))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -165,7 +175,7 @@ var _ = Describe("HostsFileResolver", func() {
|
|||
When("hosts file is provided", func() {
|
||||
It("should return configuration", func() {
|
||||
c := sut.Configuration()
|
||||
Expect(c).Should(HaveLen(3))
|
||||
Expect(c).Should(HaveLen(4))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -192,3 +202,21 @@ var _ = Describe("HostsFileResolver", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
func writeHostFile(tmpDir *TmpFolder) *TmpFile {
|
||||
return tmpDir.CreateStringFile("hosts.txt",
|
||||
"# Random comment",
|
||||
"127.0.0.1 localhost",
|
||||
"127.0.1.1 localhost2 localhost2.local.lan",
|
||||
"::1 localhost",
|
||||
"# Two empty lines to follow",
|
||||
"",
|
||||
"",
|
||||
"faaf:faaf:faaf:faaf::1 ipv6host ipv6host.local.lan",
|
||||
"192.168.2.1 ipv4host ipv4host.local.lan",
|
||||
"10.0.0.1 router0 router1 router2",
|
||||
"10.0.0.2 router3 # Another comment",
|
||||
"10.0.0.3 # Invalid entry",
|
||||
"300.300.300.300 invalid4 # Invalid IPv4",
|
||||
"abcd:efgh:ijkl::1 invalid6 # Invalud IPv6")
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -101,7 +101,7 @@ func TestBootstrap(response *dns.Msg) *Bootstrap {
|
|||
func TestDOHUpstream(fn func(request *dns.Msg) (response *dns.Msg),
|
||||
reqFn ...func(w http.ResponseWriter)) config.Upstream {
|
||||
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
|
||||
util.FatalOnError("can't read request: ", err)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/mroth/weightedrand"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -36,23 +37,56 @@ type requestResponse struct {
|
|||
err error
|
||||
}
|
||||
|
||||
// testResolver sends a test query to verify the resolver is reachable and working
|
||||
func testResolver(r *UpstreamResolver) error {
|
||||
request := newRequest("github.com.", dns.Type(dns.TypeA))
|
||||
|
||||
resp, err := r.Resolve(request)
|
||||
if err != nil || resp.RType != model.ResponseTypeRESOLVED {
|
||||
return fmt.Errorf("test resolve of upstream server failed: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewParallelBestResolver creates new resolver instance
|
||||
func NewParallelBestResolver(upstreamResolvers map[string][]config.Upstream, bootstrap *Bootstrap) (Resolver, error) {
|
||||
s := make(map[string][]*upstreamResolverStatus, len(upstreamResolvers))
|
||||
logger := logger("parallel resolver")
|
||||
s := make(map[string][]*upstreamResolverStatus)
|
||||
|
||||
for name, res := range upstreamResolvers {
|
||||
resolvers := make([]*upstreamResolverStatus, len(res))
|
||||
var resolvers []*upstreamResolverStatus
|
||||
|
||||
for i, u := range res {
|
||||
var errResolvers int
|
||||
|
||||
for _, u := range res {
|
||||
r, err := NewUpstreamResolver(u, bootstrap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
logger.Warnf("upstream group %s: %v", name, err)
|
||||
errResolvers++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers[i] = &upstreamResolverStatus{
|
||||
if bootstrap != skipUpstreamCheck {
|
||||
err = testResolver(r)
|
||||
if err != nil {
|
||||
logger.Warn(err)
|
||||
errResolvers++
|
||||
}
|
||||
}
|
||||
|
||||
resolver := &upstreamResolverStatus{
|
||||
resolver: r,
|
||||
}
|
||||
resolvers[i].lastErrorTime.Store(time.Unix(0, 0))
|
||||
resolver.lastErrorTime.Store(time.Unix(0, 0))
|
||||
resolvers = append(resolvers, resolver)
|
||||
}
|
||||
|
||||
if bootstrap != skipUpstreamCheck {
|
||||
if bootstrap.startVerifyUpstream && errResolvers == len(res) {
|
||||
return nil, fmt.Errorf("unable to reach any DNS resolvers configured for resolver group %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
s[name] = resolvers
|
||||
|
|
|
@ -26,6 +26,67 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("Some default upstream resolvers cannot be reached", func() {
|
||||
It("should start normally", func() {
|
||||
skipUpstreamCheck.startVerifyUpstream = true
|
||||
|
||||
mockUpstream := NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
|
||||
response, _ = util.NewMsgWithAnswer(request.Question[0].Name, 123, dns.Type(dns.TypeA), "123.124.122.122")
|
||||
|
||||
return
|
||||
})
|
||||
defer mockUpstream.Close()
|
||||
|
||||
upstream := map[string][]config.Upstream{
|
||||
upstreamDefaultCfgName: {
|
||||
config.Upstream{
|
||||
Host: "wrong",
|
||||
},
|
||||
mockUpstream.Start(),
|
||||
},
|
||||
}
|
||||
|
||||
_, err := NewParallelBestResolver(upstream, skipUpstreamCheck)
|
||||
Expect(err).Should(Not(HaveOccurred()))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("All default upstream resolvers cannot be reached", func() {
|
||||
var (
|
||||
upstream map[string][]config.Upstream
|
||||
b *Bootstrap
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
b = TestBootstrap(&dns.Msg{MsgHdr: dns.MsgHdr{Rcode: dns.RcodeServerFailure}})
|
||||
|
||||
upstream = map[string][]config.Upstream{
|
||||
upstreamDefaultCfgName: {
|
||||
config.Upstream{
|
||||
Host: "wrong",
|
||||
},
|
||||
config.Upstream{
|
||||
Host: "127.0.0.2",
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
It("should fail to start if strict checking is enabled", func() {
|
||||
b.startVerifyUpstream = true
|
||||
|
||||
_, err := NewParallelBestResolver(upstream, b)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should start if strict checking is disabled", func() {
|
||||
b.startVerifyUpstream = false
|
||||
|
||||
_, err := NewParallelBestResolver(upstream, b)
|
||||
Expect(err).Should(Not(HaveOccurred()))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Resolving result from fastest upstream resolver", func() {
|
||||
var (
|
||||
sut Resolver
|
||||
|
@ -310,6 +371,8 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
sut Resolver
|
||||
)
|
||||
BeforeEach(func() {
|
||||
config.GetConfig().StartVerifyUpstream = false
|
||||
|
||||
sut, _ = NewParallelBestResolver(map[string][]config.Upstream{upstreamDefaultCfgName: {
|
||||
{Host: "host1"},
|
||||
{Host: "host2"},
|
||||
|
|
|
@ -6,11 +6,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/0xERR0R/blocky/helpertest"
|
||||
"github.com/0xERR0R/blocky/querylog"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
|
@ -30,7 +29,7 @@ type SlowMockWriter struct {
|
|||
func (m *SlowMockWriter) Write(entry *querylog.LogEntry) {
|
||||
m.entries = append(m.entries, entry)
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
func (m *SlowMockWriter) CleanUp() {
|
||||
|
@ -43,29 +42,26 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
err error
|
||||
resp *Response
|
||||
m *MockResolver
|
||||
tmpDir string
|
||||
tmpDir *helpertest.TmpFolder
|
||||
mockAnswer *dns.Msg
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
mockAnswer = new(dns.Msg)
|
||||
tmpDir, err = ioutil.TempDir("", "queryLoggingResolver")
|
||||
Expect(err).Should(Succeed())
|
||||
tmpDir = helpertest.NewTmpFolder("queryLoggingResolver")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
})
|
||||
|
||||
JustBeforeEach(func() {
|
||||
sut = NewQueryLoggingResolver(sutConfig).(*QueryLoggingResolver)
|
||||
DeferCleanup(func() { close(sut.logChan) })
|
||||
m = &MockResolver{}
|
||||
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer, Reason: "reason"}, nil)
|
||||
sut.Next(m)
|
||||
})
|
||||
AfterEach(func() {
|
||||
Expect(err).Should(Succeed())
|
||||
_ = os.RemoveAll(tmpDir)
|
||||
})
|
||||
|
||||
Describe("Process request", func() {
|
||||
|
||||
When("Resolver has no configuration", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
|
@ -83,7 +79,7 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
When("Configuration with logging per client", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
Target: tmpDir,
|
||||
Target: tmpDir.Path,
|
||||
Type: config.QueryLogTypeCsvClient,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
|
@ -106,7 +102,8 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
|
||||
By("check log for client1", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
csvLines, err := readCsv(filepath.Join(tmpDir, fmt.Sprintf("%s_client1.log", time.Now().Format("2006-01-02"))))
|
||||
csvLines, err := readCsv(tmpDir.JoinPath(
|
||||
fmt.Sprintf("%s_client1.log", time.Now().Format("2006-01-02"))))
|
||||
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(csvLines).Should(Not(BeEmpty()))
|
||||
|
@ -121,7 +118,7 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
|
||||
By("check log for client2", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
csvLines, err := readCsv(filepath.Join(tmpDir,
|
||||
csvLines, err := readCsv(tmpDir.JoinPath(
|
||||
fmt.Sprintf("%s_cl_ient2_test.log", time.Now().Format("2006-01-02"))))
|
||||
|
||||
g.Expect(err).Should(Succeed())
|
||||
|
@ -138,7 +135,7 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
When("Configuration with logging in one file for all clients", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
Target: tmpDir,
|
||||
Target: tmpDir.Path,
|
||||
Type: config.QueryLogTypeCsv,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
|
@ -159,7 +156,8 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
|
||||
By("check log", func() {
|
||||
Eventually(func(g Gomega) {
|
||||
csvLines, err := readCsv(filepath.Join(tmpDir, fmt.Sprintf("%s_ALL.log", time.Now().Format("2006-01-02"))))
|
||||
csvLines, err := readCsv(tmpDir.JoinPath(
|
||||
fmt.Sprintf("%s_ALL.log", time.Now().Format("2006-01-02"))))
|
||||
|
||||
g.Expect(err).Should(Succeed())
|
||||
g.Expect(csvLines).Should(HaveLen(2))
|
||||
|
@ -196,16 +194,13 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
mockWriter := &SlowMockWriter{}
|
||||
sut.writer = mockWriter
|
||||
|
||||
// run 10000 requests
|
||||
for i := 0; i < 10000; i++ {
|
||||
resp, err = sut.Resolve(newRequestWithClient("example.com.", dns.Type(dns.TypeA), "192.168.178.25", "client1"))
|
||||
Expect(err).Should(Succeed())
|
||||
}
|
||||
Eventually(func() int {
|
||||
_, ierr := sut.Resolve(newRequestWithClient("example.com.", dns.Type(dns.TypeA), "192.168.178.25", "client1"))
|
||||
Expect(ierr).Should(Succeed())
|
||||
|
||||
// log channel is full
|
||||
Expect(sut.logChan).Should(Not(BeEmpty()))
|
||||
return len(sut.logChan)
|
||||
}, "20s", "1µs").Should(Equal(cap(sut.logChan)))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -213,7 +208,7 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
When("resolver is enabled", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
Target: tmpDir,
|
||||
Target: tmpDir.Path,
|
||||
Type: config.QueryLogTypeCsvClient,
|
||||
LogRetentionDays: 0,
|
||||
CreationAttempts: 1,
|
||||
|
@ -229,81 +224,81 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
|
||||
Describe("Clean up of query log directory", func() {
|
||||
When("fallback logger is enabled, log retention is enabled", func() {
|
||||
It("should do nothing", func() {
|
||||
|
||||
sut := NewQueryLoggingResolver(config.QueryLogConfig{
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
LogRetentionDays: 7,
|
||||
Type: config.QueryLogTypeConsole,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
}).(*QueryLoggingResolver)
|
||||
|
||||
}
|
||||
})
|
||||
It("should do nothing", func() {
|
||||
sut.doCleanUp()
|
||||
})
|
||||
})
|
||||
When("log directory contains old files", func() {
|
||||
It("should remove files older than defined log retention", func() {
|
||||
|
||||
// create 2 files, 7 and 8 days old
|
||||
dateBefore7Days := time.Now().AddDate(0, 0, -7)
|
||||
dateBefore8Days := time.Now().AddDate(0, 0, -8)
|
||||
|
||||
f1, err := os.Create(filepath.Join(tmpDir, fmt.Sprintf("%s-test.log", dateBefore7Days.Format("2006-01-02"))))
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
f2, err := os.Create(filepath.Join(tmpDir, fmt.Sprintf("%s-test.log", dateBefore8Days.Format("2006-01-02"))))
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
sut := NewQueryLoggingResolver(config.QueryLogConfig{
|
||||
Target: tmpDir,
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
Target: tmpDir.Path,
|
||||
Type: config.QueryLogTypeCsv,
|
||||
LogRetentionDays: 7,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
})
|
||||
}
|
||||
})
|
||||
It("should remove files older than defined log retention", func() {
|
||||
// create 2 files, 7 and 8 days old
|
||||
dateBefore7Days := time.Now().AddDate(0, 0, -7)
|
||||
dateBefore9Days := time.Now().AddDate(0, 0, -9)
|
||||
|
||||
sut.(*QueryLoggingResolver).doCleanUp()
|
||||
f1 := tmpDir.CreateEmptyFile(fmt.Sprintf("%s-test.log", dateBefore7Days.Format("2006-01-02")))
|
||||
Expect(f1.Error).Should(Succeed())
|
||||
|
||||
// file 1 exist
|
||||
_, err = os.Stat(f1.Name())
|
||||
Expect(err).Should(Succeed())
|
||||
f2 := tmpDir.CreateEmptyFile(fmt.Sprintf("%s-test.log", dateBefore9Days.Format("2006-01-02")))
|
||||
Expect(f2.Error).Should(Succeed())
|
||||
|
||||
// file 2 was deleted
|
||||
_, err = os.Stat(f2.Name())
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(os.IsNotExist(err)).Should(BeTrue())
|
||||
sut.doCleanUp()
|
||||
|
||||
Eventually(func(g Gomega) {
|
||||
// file 1 exist
|
||||
g.Expect(f1.Stat()).Should(Succeed())
|
||||
|
||||
// file 2 was deleted
|
||||
ierr2 := f2.Stat()
|
||||
g.Expect(ierr2).Should(HaveOccurred())
|
||||
g.Expect(os.IsNotExist(ierr2)).Should(BeTrue())
|
||||
}).Should(Succeed())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("Wrong target configuration", func() {
|
||||
When("mysql database path is wrong", func() {
|
||||
It("should use fallback", func() {
|
||||
sutConfig := config.QueryLogConfig{
|
||||
Target: "dummy",
|
||||
Type: config.QueryLogTypeMysql,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
}
|
||||
resolver := NewQueryLoggingResolver(sutConfig)
|
||||
loggingResolver := resolver.(*QueryLoggingResolver)
|
||||
Expect(loggingResolver.logType).Should(Equal(config.QueryLogTypeConsole))
|
||||
Describe("Wrong target configuration", func() {
|
||||
When("mysql database path is wrong", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
Target: "dummy",
|
||||
Type: config.QueryLogTypeMysql,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
}
|
||||
})
|
||||
It("should use fallback", func() {
|
||||
Expect(sut.logType).Should(Equal(config.QueryLogTypeConsole))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("postgresql database path is wrong", func() {
|
||||
It("should use fallback", func() {
|
||||
sutConfig := config.QueryLogConfig{
|
||||
Target: "dummy",
|
||||
Type: config.QueryLogTypePostgresql,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
}
|
||||
resolver := NewQueryLoggingResolver(sutConfig)
|
||||
loggingResolver := resolver.(*QueryLoggingResolver)
|
||||
Expect(loggingResolver.logType).Should(Equal(config.QueryLogTypeConsole))
|
||||
When("postgresql database path is wrong", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLogConfig{
|
||||
Target: "dummy",
|
||||
Type: config.QueryLogTypePostgresql,
|
||||
CreationAttempts: 1,
|
||||
CreationCooldown: config.Duration(time.Millisecond),
|
||||
}
|
||||
})
|
||||
It("should use fallback", func() {
|
||||
Expect(sut.logType).Should(Equal(config.QueryLogTypeConsole))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -315,6 +310,7 @@ func readCsv(file string) ([][]string, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer csvFile.Close()
|
||||
|
||||
reader := csv.NewReader(bufio.NewReader(csvFile))
|
||||
reader.Comma = '\t'
|
||||
|
|
|
@ -40,6 +40,23 @@ func newRequestWithClient(question string, rType dns.Type, ip string, clientName
|
|||
}
|
||||
}
|
||||
|
||||
// newResponseMsg creates a new dns.Msg as response for a request
|
||||
func newResponseMsg(request *model.Request) *dns.Msg {
|
||||
response := new(dns.Msg)
|
||||
response.SetReply(request.Req)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// returnResponseModel wrapps a dns.Msg into a model.Response
|
||||
func returnResponseModel(response *dns.Msg, rtype model.ResponseType, reason string) (*model.Response, error) {
|
||||
return &model.Response{
|
||||
Res: response,
|
||||
RType: rtype,
|
||||
Reason: reason,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newRequestWithClientID(question string, rType dns.Type, ip string, requestClientID string) *model.Request {
|
||||
return &model.Request{
|
||||
ClientIP: net.ParseIP(ip),
|
||||
|
|
|
@ -18,8 +18,9 @@ import (
|
|||
// yield a result, the normal resolving is continued.
|
||||
type RewriterResolver struct {
|
||||
NextResolver
|
||||
rewrite map[string]string
|
||||
inner Resolver
|
||||
rewrite map[string]string
|
||||
inner Resolver
|
||||
fallbackUpstream bool
|
||||
}
|
||||
|
||||
func NewRewriterResolver(cfg config.RewriteConfig, inner ChainedResolver) ChainedResolver {
|
||||
|
@ -34,8 +35,9 @@ func NewRewriterResolver(cfg config.RewriteConfig, inner ChainedResolver) Chaine
|
|||
inner.Next(NewNoOpResolver())
|
||||
|
||||
return &RewriterResolver{
|
||||
rewrite: cfg.Rewrite,
|
||||
inner: inner,
|
||||
rewrite: cfg.Rewrite,
|
||||
inner: inner,
|
||||
fallbackUpstream: cfg.FallbackUpstream,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,13 +72,23 @@ func (r *RewriterResolver) Resolve(request *model.Request) (*model.Response, err
|
|||
logger.WithField("resolver", Name(r.inner)).Trace("go to inner resolver")
|
||||
|
||||
response, err := r.inner.Resolve(request)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
// Test for error after checking for fallbackUpstream
|
||||
|
||||
// Revert the request: must be done before calling r.next
|
||||
request.Req = original
|
||||
|
||||
fallbackCondition := err != nil || (response != NoResponse && response.Res.Answer == nil)
|
||||
if r.fallbackUpstream && fallbackCondition {
|
||||
// Inner resolver had no answer, configuration requests fallback, continue with the normal chain
|
||||
logger.WithField("next_resolver", Name(r.next)).Trace("fallback to next resolver")
|
||||
|
||||
return r.next.Resolve(request)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
if response == NoResponse {
|
||||
// Inner resolver had no response, continue with the normal chain
|
||||
logger.WithField("next_resolver", Name(r.next)).Trace("go to next resolver")
|
||||
|
|
|
@ -11,6 +11,11 @@ import (
|
|||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const (
|
||||
sampleOriginal = "test.original."
|
||||
sampleRewritten = "test.rewritten."
|
||||
)
|
||||
|
||||
var _ = Describe("RewriterResolver", func() {
|
||||
var (
|
||||
sut ChainedResolver
|
||||
|
@ -53,6 +58,11 @@ var _ = Describe("RewriterResolver", func() {
|
|||
|
||||
When("has rewrite", func() {
|
||||
var request *model.Request
|
||||
var expectNilAnswer bool
|
||||
|
||||
BeforeEach(func() {
|
||||
expectNilAnswer = false
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
request = newRequest(fqdnOriginal, dns.Type(dns.TypeA))
|
||||
|
@ -80,13 +90,17 @@ var _ = Describe("RewriterResolver", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
if resp != mNextResponse {
|
||||
Expect(resp.Res.Question[0].Name).Should(Equal(fqdnOriginal))
|
||||
Expect(resp.Res.Answer[0].Header().Name).Should(Equal(fqdnOriginal))
|
||||
if expectNilAnswer {
|
||||
Expect(resp.Res.Answer).Should(BeEmpty())
|
||||
} else {
|
||||
Expect(resp.Res.Answer[0].Header().Name).Should(Equal(fqdnOriginal))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("should modify names", func() {
|
||||
fqdnOriginal = "test.original."
|
||||
fqdnRewritten = "test.rewritten."
|
||||
fqdnOriginal = sampleOriginal
|
||||
fqdnRewritten = sampleRewritten
|
||||
})
|
||||
|
||||
It("should modify subdomains", func() {
|
||||
|
@ -105,8 +119,9 @@ var _ = Describe("RewriterResolver", func() {
|
|||
})
|
||||
|
||||
It("should call next resolver", func() {
|
||||
fqdnOriginal = "test.original."
|
||||
fqdnRewritten = "test.rewritten."
|
||||
fqdnOriginal = sampleOriginal
|
||||
fqdnRewritten = sampleRewritten
|
||||
expectNilAnswer = true
|
||||
|
||||
// Make inner call the NoOpResolver
|
||||
mInner.ResolveFn = func(req *model.Request) (*model.Response, error) {
|
||||
|
@ -126,6 +141,54 @@ var _ = Describe("RewriterResolver", func() {
|
|||
return mNextResponse, nil
|
||||
}
|
||||
})
|
||||
|
||||
It("should not call next resolver", func() {
|
||||
fqdnOriginal = sampleOriginal
|
||||
fqdnRewritten = sampleRewritten
|
||||
expectNilAnswer = true
|
||||
|
||||
// Make inner return a nil Answer but not an empty Response
|
||||
mInner.ResolveFn = func(req *model.Request) (*model.Response, error) {
|
||||
Expect(req).Should(Equal(request))
|
||||
|
||||
// Inner should see fqdnRewritten
|
||||
Expect(req.Req.Question[0].Name).Should(Equal(fqdnRewritten))
|
||||
|
||||
return &model.Response{Res: &dns.Msg{Question: req.Req.Question, Answer: nil}}, nil
|
||||
}
|
||||
|
||||
// Resolver after RewriterResolver should not be called `fqdnOriginal`
|
||||
mNext.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
|
||||
When("has fallbackUpstream", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig.FallbackUpstream = true
|
||||
})
|
||||
|
||||
It("should call next resolver", func() {
|
||||
fqdnOriginal = sampleOriginal
|
||||
fqdnRewritten = sampleRewritten
|
||||
|
||||
// Make inner return a nil Answer but not an empty Response
|
||||
mInner.ResolveFn = func(req *model.Request) (*model.Response, error) {
|
||||
Expect(req).Should(Equal(request))
|
||||
|
||||
// Inner should see fqdnRewritten
|
||||
Expect(req.Req.Question[0].Name).Should(Equal(fqdnRewritten))
|
||||
|
||||
return &model.Response{Res: &dns.Msg{Question: req.Req.Question, Answer: nil}}, nil
|
||||
}
|
||||
|
||||
// Resolver after RewriterResolver should see `fqdnOriginal`
|
||||
mNext.On("Resolve", mock.Anything)
|
||||
mNext.ResolveFn = func(req *model.Request) (*model.Response, error) {
|
||||
Expect(req.Req.Question[0].Name).Should(Equal(fqdnOriginal))
|
||||
|
||||
return mNextResponse, nil
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Configuration output", func() {
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const (
|
||||
sudnTest = "test."
|
||||
sudnInvalid = "invalid."
|
||||
sudnLocalhost = "localhost."
|
||||
mdnsLocal = "local."
|
||||
)
|
||||
|
||||
func sudnArpaSlice() []string {
|
||||
return []string{
|
||||
"10.in-addr.arpa.",
|
||||
"21.172.in-addr.arpa.",
|
||||
"26.172.in-addr.arpa.",
|
||||
"16.172.in-addr.arpa.",
|
||||
"22.172.in-addr.arpa.",
|
||||
"27.172.in-addr.arpa.",
|
||||
"17.172.in-addr.arpa.",
|
||||
"30.172.in-addr.arpa.",
|
||||
"28.172.in-addr.arpa.",
|
||||
"18.172.in-addr.arpa.",
|
||||
"23.172.in-addr.arpa.",
|
||||
"29.172.in-addr.arpa.",
|
||||
"19.172.in-addr.arpa.",
|
||||
"24.172.in-addr.arpa.",
|
||||
"31.172.in-addr.arpa.",
|
||||
"20.172.in-addr.arpa.",
|
||||
"25.172.in-addr.arpa.",
|
||||
"168.192.in-addr.arpa.",
|
||||
}
|
||||
}
|
||||
|
||||
type defaultIPs struct {
|
||||
loopbackV4 net.IP
|
||||
loopbackV6 net.IP
|
||||
}
|
||||
|
||||
type SpecialUseDomainNamesResolver struct {
|
||||
NextResolver
|
||||
defaults *defaultIPs
|
||||
}
|
||||
|
||||
func NewSpecialUseDomainNamesResolver() ChainedResolver {
|
||||
return &SpecialUseDomainNamesResolver{
|
||||
defaults: &defaultIPs{
|
||||
loopbackV4: net.ParseIP("127.0.0.1"),
|
||||
loopbackV6: net.IPv6loopback,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SpecialUseDomainNamesResolver) Resolve(request *model.Request) (*model.Response, error) {
|
||||
// RFC 6761 - negative
|
||||
if r.isSpecial(request, sudnArpaSlice()...) ||
|
||||
r.isSpecial(request, sudnInvalid) ||
|
||||
r.isSpecial(request, sudnTest) {
|
||||
return r.negativeResponse(request)
|
||||
}
|
||||
// RFC 6761 - switched
|
||||
if r.isSpecial(request, sudnLocalhost) {
|
||||
return r.responseSwitch(request, sudnLocalhost, r.defaults.loopbackV4, r.defaults.loopbackV6)
|
||||
}
|
||||
|
||||
// RFC 6762 - negative
|
||||
if r.isSpecial(request, mdnsLocal) {
|
||||
return r.negativeResponse(request)
|
||||
}
|
||||
|
||||
return r.next.Resolve(request)
|
||||
}
|
||||
|
||||
// RFC 6761 & 6762 are always active
|
||||
func (r *SpecialUseDomainNamesResolver) Configuration() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (r *SpecialUseDomainNamesResolver) isSpecial(request *model.Request, names ...string) bool {
|
||||
domainFromQuestion := request.Req.Question[0].Name
|
||||
for _, n := range names {
|
||||
if domainFromQuestion == n ||
|
||||
strings.HasSuffix(domainFromQuestion, fmt.Sprintf(".%s", n)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *SpecialUseDomainNamesResolver) responseSwitch(request *model.Request,
|
||||
name string, ipV4, ipV6 net.IP) (*model.Response, error) {
|
||||
qtype := request.Req.Question[0].Qtype
|
||||
switch qtype {
|
||||
case dns.TypeA:
|
||||
return r.positiveResponse(request, name, dns.TypeA, ipV4)
|
||||
case dns.TypeAAAA:
|
||||
return r.positiveResponse(request, name, dns.TypeAAAA, ipV6)
|
||||
default:
|
||||
return r.negativeResponse(request)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SpecialUseDomainNamesResolver) positiveResponse(request *model.Request,
|
||||
name string, rtype uint16, ip net.IP) (*model.Response, error) {
|
||||
response := newResponseMsg(request)
|
||||
response.Rcode = dns.RcodeSuccess
|
||||
|
||||
hdr := dns.RR_Header{
|
||||
Name: name,
|
||||
Rrtype: rtype,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 0,
|
||||
}
|
||||
|
||||
if rtype != dns.TypeA && rtype != dns.TypeAAAA {
|
||||
return nil, fmt.Errorf("invalid response type")
|
||||
}
|
||||
|
||||
var rr dns.RR
|
||||
if rtype == dns.TypeA {
|
||||
rr = &dns.A{
|
||||
A: ip,
|
||||
Hdr: hdr,
|
||||
}
|
||||
} else {
|
||||
rr = &dns.AAAA{
|
||||
AAAA: ip,
|
||||
Hdr: hdr,
|
||||
}
|
||||
}
|
||||
|
||||
response.Answer = []dns.RR{rr}
|
||||
|
||||
return r.returnResponseModel(response)
|
||||
}
|
||||
|
||||
func (r *SpecialUseDomainNamesResolver) negativeResponse(request *model.Request) (*model.Response, error) {
|
||||
response := newResponseMsg(request)
|
||||
response.Rcode = dns.RcodeNameError
|
||||
|
||||
return r.returnResponseModel(response)
|
||||
}
|
||||
|
||||
func (r *SpecialUseDomainNamesResolver) returnResponseModel(response *dns.Msg) (*model.Response, error) {
|
||||
return returnResponseModel(response, model.ResponseTypeSPECIAL, "Special-Use Domain Name")
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package resolver
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
. "github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var _ = Describe("SudnResolver", Label("sudnResolver"), func() {
|
||||
var (
|
||||
sut *SpecialUseDomainNamesResolver
|
||||
m *MockResolver
|
||||
mockAnswer *dns.Msg
|
||||
|
||||
err error
|
||||
resp *Response
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
mockAnswer, err = util.NewMsgWithAnswer("example.com.", 300, dns.Type(dns.TypeA), "123.145.123.145")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
m = &MockResolver{}
|
||||
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer}, nil)
|
||||
|
||||
sut = NewSpecialUseDomainNamesResolver().(*SpecialUseDomainNamesResolver)
|
||||
sut.Next(m)
|
||||
})
|
||||
|
||||
Describe("Blocking special names", func() {
|
||||
It("should block arpa", func() {
|
||||
for _, arpa := range sudnArpaSlice() {
|
||||
resp, err = sut.Resolve(newRequest(arpa, dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
}
|
||||
})
|
||||
|
||||
It("should block test", func() {
|
||||
resp, err = sut.Resolve(newRequest(sudnTest, dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
})
|
||||
|
||||
It("should block invalid", func() {
|
||||
resp, err = sut.Resolve(newRequest(sudnInvalid, dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
})
|
||||
|
||||
It("should block localhost none A", func() {
|
||||
resp, err = sut.Resolve(newRequest(sudnLocalhost, dns.Type(dns.TypeHTTPS)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
})
|
||||
|
||||
It("should block local", func() {
|
||||
resp, err = sut.Resolve(newRequest(mdnsLocal, dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
})
|
||||
|
||||
It("should block localhost none A", func() {
|
||||
resp, err = sut.Resolve(newRequest(mdnsLocal, dns.Type(dns.TypeHTTPS)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Resolve localhost", func() {
|
||||
It("should resolve IPv4 loopback", func() {
|
||||
resp, err = sut.Resolve(newRequest(sudnLocalhost, dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.Res.Answer[0].(*dns.A).A).Should(Equal(sut.defaults.loopbackV4))
|
||||
})
|
||||
|
||||
It("should resolve IPv6 loopback", func() {
|
||||
resp, err = sut.Resolve(newRequest(sudnLocalhost, dns.Type(dns.TypeAAAA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.Res.Answer[0].(*dns.AAAA).AAAA).Should(Equal(sut.defaults.loopbackV6))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Forward other", func() {
|
||||
It("should forward example.com", func() {
|
||||
resp, err = sut.Resolve(newRequest("example.com", dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
|
||||
Expect(resp.Res.Answer[0].(*dns.A).A).Should(Equal(net.ParseIP("123.145.123.145")))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Configuration pseudo test", func() {
|
||||
It("should always be empty", func() {
|
||||
Expect(sut.Configuration()).Should(HaveLen(0))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -5,7 +5,7 @@ import (
|
|||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
@ -53,15 +53,21 @@ type dnsUpstreamClient struct {
|
|||
|
||||
type httpUpstreamClient struct {
|
||||
client *http.Client
|
||||
host string
|
||||
}
|
||||
|
||||
func createUpstreamClient(cfg config.Upstream) upstreamClient {
|
||||
timeout := time.Duration(config.GetConfig().UpstreamTimeout)
|
||||
|
||||
tlsConfig := tls.Config{
|
||||
ServerName: cfg.Host,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
if cfg.CommonName != "" {
|
||||
tlsConfig.ServerName = cfg.CommonName
|
||||
}
|
||||
|
||||
switch cfg.Net {
|
||||
case config.NetProtocolHttps:
|
||||
return &httpUpstreamClient{
|
||||
|
@ -73,6 +79,7 @@ func createUpstreamClient(cfg config.Upstream) upstreamClient {
|
|||
},
|
||||
Timeout: timeout,
|
||||
},
|
||||
host: cfg.Host,
|
||||
}
|
||||
|
||||
case config.NetProtocolTcpTls:
|
||||
|
@ -106,7 +113,7 @@ func createUpstreamClient(cfg config.Upstream) upstreamClient {
|
|||
}
|
||||
|
||||
func (r *httpUpstreamClient) fmtURL(ip net.IP, port uint16, path string) string {
|
||||
return fmt.Sprintf("https://%s:%d%s", ip.String(), port, path)
|
||||
return fmt.Sprintf("https://%s%s", net.JoinHostPort(ip.String(), strconv.Itoa(int(port))), path)
|
||||
}
|
||||
|
||||
func (r *httpUpstreamClient) callExternal(msg *dns.Msg,
|
||||
|
@ -127,6 +134,8 @@ func (r *httpUpstreamClient) callExternal(msg *dns.Msg,
|
|||
|
||||
req.Header.Set("User-Agent", config.GetConfig().DoHUserAgent)
|
||||
req.Header.Set("Content-Type", dnsContentType)
|
||||
req.Host = r.host
|
||||
|
||||
httpResponse, err := r.client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
|
@ -147,7 +156,7 @@ func (r *httpUpstreamClient) callExternal(msg *dns.Msg,
|
|||
dnsContentType, contentType)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(httpResponse.Body)
|
||||
body, err := io.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("can't read response body: %w", err)
|
||||
}
|
||||
|
@ -162,7 +171,7 @@ func (r *httpUpstreamClient) callExternal(msg *dns.Msg,
|
|||
return &response, time.Since(start), nil
|
||||
}
|
||||
|
||||
func (r *dnsUpstreamClient) fmtURL(ip net.IP, port uint16, _path string) string {
|
||||
func (r *dnsUpstreamClient) fmtURL(ip net.IP, port uint16, _ string) string {
|
||||
return net.JoinHostPort(ip.String(), strconv.Itoa(int(port)))
|
||||
}
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
It("should return error", func() {
|
||||
_, err := sut.Resolve(newRequest("example.com.", dns.Type(dns.TypeA)))
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("no such host"))
|
||||
Expect(err.Error()).Should(Or(ContainSubstring("no such host"), ContainSubstring("i/o timeout")))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,12 +2,14 @@ package server
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
|
@ -36,7 +38,6 @@ const (
|
|||
maxUDPBufferSize = 65535
|
||||
caExpiryYears = 10
|
||||
certExpiryYears = 5
|
||||
certRSAsize = 4096
|
||||
)
|
||||
|
||||
// Server controls the endpoints for DNS and HTTP
|
||||
|
@ -295,7 +296,7 @@ func createUDPServer(address string) (*dns.Server, error) {
|
|||
func createSelfSignedCert() (tls.Certificate, error) {
|
||||
// Create CA
|
||||
ca := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(int64(mrand.Intn(certRSAsize))), //nolint:gosec
|
||||
SerialNumber: big.NewInt(int64(mrand.Intn(math.MaxInt))), //nolint:gosec
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(caExpiryYears, 0, 0),
|
||||
IsCA: true,
|
||||
|
@ -304,7 +305,7 @@ func createSelfSignedCert() (tls.Certificate, error) {
|
|||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
caPrivKey, err := rsa.GenerateKey(rand.Reader, certRSAsize)
|
||||
caPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
@ -323,16 +324,22 @@ func createSelfSignedCert() (tls.Certificate, error) {
|
|||
}
|
||||
|
||||
caPrivKeyPEM := new(bytes.Buffer)
|
||||
|
||||
b, err := x509.MarshalECPrivateKey(caPrivKey)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
if err = pem.Encode(caPrivKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: b,
|
||||
}); err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
// Create certificate
|
||||
cert := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(int64(mrand.Intn(certRSAsize))), //nolint:gosec
|
||||
SerialNumber: big.NewInt(int64(mrand.Intn(math.MaxInt))), //nolint:gosec
|
||||
DNSNames: []string{"*"},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(certExpiryYears, 0, 0),
|
||||
|
@ -341,7 +348,7 @@ func createSelfSignedCert() (tls.Certificate, error) {
|
|||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
|
||||
certPrivKey, err := rsa.GenerateKey(rand.Reader, certRSAsize)
|
||||
certPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
@ -360,9 +367,15 @@ func createSelfSignedCert() (tls.Certificate, error) {
|
|||
}
|
||||
|
||||
certPrivKeyPEM := new(bytes.Buffer)
|
||||
|
||||
b, err = x509.MarshalECPrivateKey(certPrivKey)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
||||
if err = pem.Encode(certPrivKeyPEM, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: b,
|
||||
}); err != nil {
|
||||
return tls.Certificate{}, err
|
||||
}
|
||||
|
@ -397,7 +410,9 @@ func createQueryResolver(
|
|||
|
||||
r = resolver.Chain(
|
||||
resolver.NewFilteringResolver(cfg.Filtering),
|
||||
resolver.NewFqdnOnlyResolver(*cfg),
|
||||
clientNamesResolver,
|
||||
resolver.NewEdeResolver(cfg.Ede),
|
||||
resolver.NewQueryLoggingResolver(cfg.QueryLog),
|
||||
resolver.NewMetricsResolver(cfg.Prometheus),
|
||||
resolver.NewRewriterResolver(cfg.CustomDNS.RewriteConfig, resolver.NewCustomDNSResolver(cfg.CustomDNS)),
|
||||
|
@ -405,6 +420,7 @@ func createQueryResolver(
|
|||
blockingResolver,
|
||||
resolver.NewCachingResolver(cfg.Caching, redisClient),
|
||||
resolver.NewRewriterResolver(cfg.Conditional.RewriteConfig, conditionalUpstreamResolver),
|
||||
resolver.NewSpecialUseDomainNamesResolver(),
|
||||
parallelResolver,
|
||||
)
|
||||
|
||||
|
@ -467,6 +483,12 @@ func toMB(b uint64) uint64 {
|
|||
return b / bytesInKB / bytesInKB
|
||||
}
|
||||
|
||||
const (
|
||||
readHeaderTimeout = 20 * time.Second
|
||||
readTimeout = 20 * time.Second
|
||||
writeTimeout = 20 * time.Second
|
||||
)
|
||||
|
||||
// Start starts the server
|
||||
func (s *Server) Start(errCh chan<- error) {
|
||||
logger().Info("Starting server")
|
||||
|
@ -488,7 +510,14 @@ func (s *Server) Start(errCh chan<- error) {
|
|||
go func() {
|
||||
logger().Infof("http server is up and running on addr/port %s", address)
|
||||
|
||||
if err := http.Serve(listener, s.httpMux); err != nil {
|
||||
srv := &http.Server{
|
||||
ReadTimeout: readTimeout,
|
||||
ReadHeaderTimeout: readHeaderTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
Handler: s.httpsMux,
|
||||
}
|
||||
|
||||
if err := srv.Serve(listener); err != nil {
|
||||
errCh <- fmt.Errorf("start http listener failed: %w", err)
|
||||
}
|
||||
}()
|
||||
|
@ -502,7 +531,10 @@ func (s *Server) Start(errCh chan<- error) {
|
|||
logger().Infof("https server is up and running on addr/port %s", address)
|
||||
|
||||
server := http.Server{
|
||||
Handler: s.httpsMux,
|
||||
Handler: s.httpsMux,
|
||||
ReadTimeout: readTimeout,
|
||||
ReadHeaderTimeout: readHeaderTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
//nolint:gosec
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: minTLSVersion(),
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -25,9 +25,12 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
dohMessageLimit = 512
|
||||
dnsContentType = "application/dns-message"
|
||||
corsMaxAge = 5 * time.Minute
|
||||
dohMessageLimit = 512
|
||||
contentTypeHeader = "content-type"
|
||||
dnsContentType = "application/dns-message"
|
||||
jsonContentType = "application/json"
|
||||
htmlContentType = "text/html; charset=UTF-8"
|
||||
corsMaxAge = 5 * time.Minute
|
||||
)
|
||||
|
||||
func secureHeader(next http.Handler) http.Handler {
|
||||
|
@ -83,7 +86,7 @@ func (s *Server) dohPostRequestHandler(rw http.ResponseWriter, req *http.Request
|
|||
return
|
||||
}
|
||||
|
||||
rawMsg, err := ioutil.ReadAll(req.Body)
|
||||
rawMsg, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
|
||||
|
@ -173,6 +176,9 @@ func extractIP(r *http.Request) string {
|
|||
// @Router /query [post]
|
||||
func (s *Server) apiQuery(rw http.ResponseWriter, req *http.Request) {
|
||||
var queryRequest api.QueryRequest
|
||||
|
||||
rw.Header().Set(contentTypeHeader, jsonContentType)
|
||||
|
||||
err := json.NewDecoder(req.Body).Decode(&queryRequest)
|
||||
|
||||
if err != nil {
|
||||
|
@ -253,7 +259,7 @@ func createRouter(cfg *config.Config) *chi.Mux {
|
|||
|
||||
func configureRootHandler(cfg *config.Config, router *chi.Mux) {
|
||||
router.Get("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.Header().Set("content-type", dnsContentType)
|
||||
writer.Header().Set(contentTypeHeader, htmlContentType)
|
||||
t := template.New("index")
|
||||
_, _ = t.Parse(web.IndexTmpl)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -61,8 +61,15 @@ var _ = BeforeSuite(func() {
|
|||
DeferCleanup(fritzboxMockUpstream.Close)
|
||||
|
||||
clientMockUpstream := resolver.NewMockUDPUpstreamServer().WithAnswerFn(func(request *dns.Msg) (response *dns.Msg) {
|
||||
var clientName string
|
||||
client := mockClientName.Load()
|
||||
|
||||
if client != nil {
|
||||
clientName = mockClientName.Load().(string)
|
||||
}
|
||||
|
||||
response, err := util.NewMsgWithAnswer(
|
||||
util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypePTR), mockClientName.Load().(string),
|
||||
util.ExtractDomain(request.Question[0]), 3600, dns.Type(dns.TypePTR), clientName,
|
||||
)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -75,6 +82,28 @@ var _ = BeforeSuite(func() {
|
|||
upstreamFritzbox = fritzboxMockUpstream.Start()
|
||||
upstreamGoogle = googleMockUpstream.Start()
|
||||
|
||||
tmpDir := NewTmpFolder("server")
|
||||
Expect(tmpDir.Error).Should(Succeed())
|
||||
DeferCleanup(tmpDir.Clean)
|
||||
|
||||
certPem := writeCertPem(tmpDir)
|
||||
Expect(certPem.Error).Should(Succeed())
|
||||
|
||||
keyPem := writeKeyPem(tmpDir)
|
||||
Expect(keyPem.Error).Should(Succeed())
|
||||
|
||||
doubleclickFile := tmpDir.CreateStringFile("doubleclick.net.txt", "doubleclick.net", "doubleclick.net.cn")
|
||||
Expect(doubleclickFile.Error).Should(Succeed())
|
||||
|
||||
bildFile := tmpDir.CreateStringFile("www.bild.de.txt", "www.bild.de")
|
||||
Expect(bildFile.Error).Should(Succeed())
|
||||
|
||||
heiseFile := tmpDir.CreateStringFile("heise.de.txt", "heise.de")
|
||||
Expect(heiseFile.Error).Should(Succeed())
|
||||
|
||||
youtubeFile := tmpDir.CreateStringFile("youtube.com.txt", "youtube.com")
|
||||
Expect(youtubeFile.Error).Should(Succeed())
|
||||
|
||||
// create server
|
||||
sut, err = NewServer(&config.Config{
|
||||
CustomDNS: config.CustomDNSConfig{
|
||||
|
@ -97,13 +126,13 @@ var _ = BeforeSuite(func() {
|
|||
Blocking: config.BlockingConfig{
|
||||
BlackLists: map[string][]string{
|
||||
"ads": {
|
||||
"../testdata/doubleclick.net.txt",
|
||||
"../testdata/www.bild.de.txt",
|
||||
"../testdata/heise.de.txt"},
|
||||
"youtube": {"../testdata/youtube.com.txt"}},
|
||||
doubleclickFile.Path,
|
||||
bildFile.Path,
|
||||
heiseFile.Path},
|
||||
"youtube": {youtubeFile.Path}},
|
||||
WhiteLists: map[string][]string{
|
||||
"ads": {"../testdata/heise.de.txt"},
|
||||
"whitelist": {"../testdata/heise.de.txt"},
|
||||
"ads": {heiseFile.Path},
|
||||
"whitelist": {heiseFile.Path},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"ads"},
|
||||
|
@ -123,8 +152,8 @@ var _ = BeforeSuite(func() {
|
|||
|
||||
DNSPorts: config.ListenConfig{"55555"},
|
||||
TLSPorts: config.ListenConfig{"8853"},
|
||||
CertFile: "../testdata/cert.pem",
|
||||
KeyFile: "../testdata/key.pem",
|
||||
CertFile: certPem.Path,
|
||||
KeyFile: keyPem.Path,
|
||||
HTTPPorts: config.ListenConfig{"4000"},
|
||||
HTTPSPorts: config.ListenConfig{"4443"},
|
||||
Prometheus: config.PrometheusConfig{
|
||||
|
@ -290,18 +319,19 @@ var _ = Describe("Running DNS server", func() {
|
|||
Describe("Prometheus endpoint", func() {
|
||||
When("Prometheus URL is called", func() {
|
||||
It("should return prometheus data", func() {
|
||||
r, err := http.Get("http://localhost:4000/metrics")
|
||||
resp, err := http.Get("http://localhost:4000/metrics")
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(r.StatusCode).Should(Equal(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
})
|
||||
})
|
||||
})
|
||||
Describe("Root endpoint", func() {
|
||||
When("Root URL is called", func() {
|
||||
It("should return root page", func() {
|
||||
r, err := http.Get("http://localhost:4000/")
|
||||
resp, err := http.Get("http://localhost:4000/")
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(r.StatusCode).Should(Equal(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "text/html; charset=UTF-8"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -321,7 +351,8 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
defer resp.Body.Close()
|
||||
|
||||
Expect(resp.StatusCode).Should(Equal(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/json"))
|
||||
|
||||
var result api.QueryResult
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
|
@ -384,7 +415,9 @@ var _ = Describe("Running DNS server", func() {
|
|||
defer resp.Body.Close()
|
||||
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
rawMsg, err := ioutil.ReadAll(resp.Body)
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/dns-message"))
|
||||
|
||||
rawMsg, err := io.ReadAll(resp.Body)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
msg := new(dns.Msg)
|
||||
|
@ -446,7 +479,8 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
defer resp.Body.Close()
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
rawMsg, err := ioutil.ReadAll(resp.Body)
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/dns-message"))
|
||||
rawMsg, err := io.ReadAll(resp.Body)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
msg = new(dns.Msg)
|
||||
|
@ -465,7 +499,8 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
defer resp.Body.Close()
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
rawMsg, err := ioutil.ReadAll(resp.Body)
|
||||
Expect(resp).Should(HaveHTTPHeaderWithValue("Content-type", "application/dns-message"))
|
||||
rawMsg, err := io.ReadAll(resp.Body)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
msg = new(dns.Msg)
|
||||
|
@ -522,7 +557,7 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(cErr).Should(Succeed())
|
||||
|
||||
cfg.Upstream.ExternalResolvers = map[string][]config.Upstream{
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}}}
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}}}
|
||||
|
||||
cfg.Redis.Address = "test-fail"
|
||||
})
|
||||
|
@ -652,7 +687,7 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(cErr).Should(Succeed())
|
||||
|
||||
cfg.Upstream.ExternalResolvers = map[string][]config.Upstream{
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "4.4.4.4", Port: 53}}}
|
||||
"default": {config.Upstream{Net: config.NetProtocolTcpUdp, Host: "1.1.1.1", Port: 53}}}
|
||||
})
|
||||
|
||||
It("should create self-signed certificate if key/cert files are not provided", func() {
|
||||
|
@ -701,3 +736,41 @@ func requestServer(request *dns.Msg) *dns.Msg {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeCertPem(tmpDir *TmpFolder) *TmpFile {
|
||||
return tmpDir.CreateStringFile("cert.pem",
|
||||
"-----BEGIN CERTIFICATE-----",
|
||||
"MIICMzCCAZygAwIBAgIRAJCCrDTGEtZfRpxDY1KAoswwDQYJKoZIhvcNAQELBQAw",
|
||||
"EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2",
|
||||
"MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw",
|
||||
"gYkCgYEA4mEaF5yWYYrTfMgRXdBpgGnqsHIADQWlw7BIJWD/gNp+fgp4TUZ/7ggV",
|
||||
"rrvRORvRFjw14avd9L9EFP7XLi8ViU3uoE1UWI32MlrKqLbGNCXyUIApIoqlbRg6",
|
||||
"iErxIk5+ChzFuysQOx01S2yv/ML6dx7NOGHs1S38MUzRZtcXBH8CAwEAAaOBhjCB",
|
||||
"gzAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/",
|
||||
"BAUwAwEB/zAdBgNVHQ4EFgQUslNI6tYIv909RttHaZVMS/u/VYYwLAYDVR0RBCUw",
|
||||
"I4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEB",
|
||||
"CwUAA4GBAJ2gRpQHr5Qj7dt26bYVMdN4JGXTsvjbVrJfKI0VfPGJ+SUY/uTVBUeX",
|
||||
"+Cwv4DFEPBlNx/lzuUkwmRaExC4/w81LWwxe5KltYsjyJuYowiUbLZ6tzLaQ9Bcx",
|
||||
"jxClAVvgj90TGYOwsv6ESOX7GWteN1FlD3+jk7vefjFagaKKFYR9",
|
||||
"-----END CERTIFICATE-----")
|
||||
}
|
||||
|
||||
func writeKeyPem(tmpDir *TmpFolder) *TmpFile {
|
||||
return tmpDir.CreateStringFile("key.pem",
|
||||
"-----BEGIN PRIVATE KEY-----",
|
||||
"MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOJhGheclmGK03zI",
|
||||
"EV3QaYBp6rByAA0FpcOwSCVg/4Dafn4KeE1Gf+4IFa670Tkb0RY8NeGr3fS/RBT+",
|
||||
"1y4vFYlN7qBNVFiN9jJayqi2xjQl8lCAKSKKpW0YOohK8SJOfgocxbsrEDsdNUts",
|
||||
"r/zC+ncezThh7NUt/DFM0WbXFwR/AgMBAAECgYEA1exixstPhI+2+OTrHFc1S4dL",
|
||||
"oz+ncqbSlZEBLGl0KWTQQfVM5+FmRR7Yto1/0lLKDBQL6t0J2x3fjWOhHmCaHKZA",
|
||||
"VAvZ8+OKxwofih3hlO0tGCB8szUJygp2FAmd0rOUqvPQ+PTohZEUXyDaB8MOIbX+",
|
||||
"qoo7g19+VlbyKqmM8HkCQQDs4GQJwEn7GXKllSMyOfiYnjQM2pwsqO0GivXkH+p3",
|
||||
"+h5KDp4g3O4EbmbrvZyZB2euVsBjW3pFMu+xPXuOXf91AkEA9KfC7LGLD2OtLmrM",
|
||||
"iCZAqHlame+uEEDduDmqjTPnNKUWVeRtYKMF5Hltbeo1jMXMSbVZ+fRWKfQ+HAhQ",
|
||||
"xjFJowJAV6U7PqRoe0FSO1QwXrA2fHnk9nCY4qlqckZObyckAVqJhIteFPjKFNeo",
|
||||
"u0dAPxsPUOGGc/zwA9Sx/ZmrMuUy1QJBALl7bqawO/Ng6G0mfwZBqgeQaYYHVnnw",
|
||||
"E6iV353J2eHpvzNDSUFYlyEOhk4soIindSf0m9CK08Be8a+jBkocF+0CQQC+Hi7L",
|
||||
"kZV1slpW82BxYIhs9Gb0OQgK8SsI4aQPTFGUarQXXAm4eRqBO0kaG+jGX6TtW353",
|
||||
"EHK784GIxwVXKej/",
|
||||
"-----END PRIVATE KEY-----")
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICMzCCAZygAwIBAgIRAJCCrDTGEtZfRpxDY1KAoswwDQYJKoZIhvcNAQELBQAw
|
||||
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
|
||||
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
|
||||
gYkCgYEA4mEaF5yWYYrTfMgRXdBpgGnqsHIADQWlw7BIJWD/gNp+fgp4TUZ/7ggV
|
||||
rrvRORvRFjw14avd9L9EFP7XLi8ViU3uoE1UWI32MlrKqLbGNCXyUIApIoqlbRg6
|
||||
iErxIk5+ChzFuysQOx01S2yv/ML6dx7NOGHs1S38MUzRZtcXBH8CAwEAAaOBhjCB
|
||||
gzAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/
|
||||
BAUwAwEB/zAdBgNVHQ4EFgQUslNI6tYIv909RttHaZVMS/u/VYYwLAYDVR0RBCUw
|
||||
I4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEB
|
||||
CwUAA4GBAJ2gRpQHr5Qj7dt26bYVMdN4JGXTsvjbVrJfKI0VfPGJ+SUY/uTVBUeX
|
||||
+Cwv4DFEPBlNx/lzuUkwmRaExC4/w81LWwxe5KltYsjyJuYowiUbLZ6tzLaQ9Bcx
|
||||
jxClAVvgj90TGYOwsv6ESOX7GWteN1FlD3+jk7vefjFagaKKFYR9
|
||||
-----END CERTIFICATE-----
|
|
@ -1,54 +0,0 @@
|
|||
upstream:
|
||||
default:
|
||||
- tcp+udp:8.8.8.8
|
||||
- tcp+udp:8.8.4.4
|
||||
- 1.1.1.1
|
||||
customDNS:
|
||||
mapping:
|
||||
my.duckdns.org: 192.168.178.3
|
||||
multiple.ips: 192.168.178.3,192.168.178.4,2001:0db8:85a3:08d3:1319:8a2e:0370:7344
|
||||
conditional:
|
||||
mapping:
|
||||
fritz.box: tcp+udp:192.168.178.1
|
||||
multiple.resolvers: tcp+udp:192.168.178.1,tcp+udp:192.168.178.2
|
||||
filtering:
|
||||
queryTypes:
|
||||
- AAAA
|
||||
- A
|
||||
blocking:
|
||||
blackLists:
|
||||
ads:
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
- https://mirror1.malwaredomains.com/files/justdomains
|
||||
- http://sysctl.org/cameleon/hosts
|
||||
- https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
|
||||
special:
|
||||
- https://hosts-file.net/ad_servers.txt
|
||||
whiteLists:
|
||||
ads:
|
||||
- whitelist.txt
|
||||
clientGroupsBlock:
|
||||
default:
|
||||
- ads
|
||||
- special
|
||||
Laptop-D.fritz.box:
|
||||
- ads
|
||||
blockTTL: 1m
|
||||
# without unit -> use minutes
|
||||
refreshPeriod: 120
|
||||
clientLookup:
|
||||
upstream: 192.168.178.1
|
||||
singleNameOrder:
|
||||
- 2
|
||||
- 1
|
||||
|
||||
queryLog:
|
||||
type: csv-client
|
||||
target: /opt/log
|
||||
|
||||
port: 55553,:55554,[::1]:55555
|
||||
logLevel: debug
|
||||
dohUserAgent: testBlocky
|
||||
minTlsServeVersion: 1.3
|
|
@ -1,18 +0,0 @@
|
|||
upstream:
|
||||
default:
|
||||
- tcp+udp:8.8.8.8
|
||||
- tcp+udp:8.8.4.4
|
||||
- 1.1.1.1
|
||||
customDNS:
|
||||
mapping:
|
||||
my.duckdns.org: 192.168.178.3
|
||||
multiple.ips: 192.168.178.3,192.168.178.4,2001:0db8:85a3:08d3:1319:8a2e:0370:7344
|
||||
conditional:
|
||||
mapping:
|
||||
fritz.box: tcp+udp:192.168.178.1
|
||||
multiple.resolvers: tcp+udp:192.168.178.1,tcp+udp:192.168.178.2
|
||||
filtering:
|
||||
queryTypes:
|
||||
- AAAA
|
||||
- A
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
blocking:
|
||||
blackLists:
|
||||
ads:
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
- https://mirror1.malwaredomains.com/files/justdomains
|
||||
- http://sysctl.org/cameleon/hosts
|
||||
- https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt
|
||||
special:
|
||||
- https://hosts-file.net/ad_servers.txt
|
||||
whiteLists:
|
||||
ads:
|
||||
- whitelist.txt
|
||||
clientGroupsBlock:
|
||||
default:
|
||||
- ads
|
||||
- special
|
||||
Laptop-D.fritz.box:
|
||||
- ads
|
||||
blockTTL: 1m
|
||||
# without unit -> use minutes
|
||||
refreshPeriod: 120
|
||||
clientLookup:
|
||||
upstream: 192.168.178.1
|
||||
singleNameOrder:
|
||||
- 2
|
||||
- 1
|
||||
|
||||
queryLog:
|
||||
type: csv-client
|
||||
target: /opt/log
|
||||
|
||||
port: 55553,:55554,[::1]:55555
|
||||
logLevel: debug
|
||||
dohUserAgent: testBlocky
|
||||
minTlsServeVersion: 1.3
|
|
@ -1,2 +0,0 @@
|
|||
doubleclick.net
|
||||
doubleclick.net.cn
|
|
@ -1 +0,0 @@
|
|||
heise.de
|
|
@ -1,14 +0,0 @@
|
|||
# Random comment
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 localhost2 localhost2.local.lan
|
||||
::1 localhost
|
||||
# Two empty lines to follow
|
||||
|
||||
|
||||
faaf:faaf:faaf:faaf::1 ipv6host ipv6host.local.lan
|
||||
192.168.2.1 ipv4host ipv4host.local.lan
|
||||
10.0.0.1 router0 router1 router2
|
||||
10.0.0.2 router3 # Another comment
|
||||
10.0.0.3 # Invalid entry
|
||||
300.300.300.300 invalid4 # Invalid IPv4
|
||||
abcd:efgh:ijkl::1 invalid6 # Invalud IPv6
|
|
@ -1,16 +0,0 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOJhGheclmGK03zI
|
||||
EV3QaYBp6rByAA0FpcOwSCVg/4Dafn4KeE1Gf+4IFa670Tkb0RY8NeGr3fS/RBT+
|
||||
1y4vFYlN7qBNVFiN9jJayqi2xjQl8lCAKSKKpW0YOohK8SJOfgocxbsrEDsdNUts
|
||||
r/zC+ncezThh7NUt/DFM0WbXFwR/AgMBAAECgYEA1exixstPhI+2+OTrHFc1S4dL
|
||||
oz+ncqbSlZEBLGl0KWTQQfVM5+FmRR7Yto1/0lLKDBQL6t0J2x3fjWOhHmCaHKZA
|
||||
VAvZ8+OKxwofih3hlO0tGCB8szUJygp2FAmd0rOUqvPQ+PTohZEUXyDaB8MOIbX+
|
||||
qoo7g19+VlbyKqmM8HkCQQDs4GQJwEn7GXKllSMyOfiYnjQM2pwsqO0GivXkH+p3
|
||||
+h5KDp4g3O4EbmbrvZyZB2euVsBjW3pFMu+xPXuOXf91AkEA9KfC7LGLD2OtLmrM
|
||||
iCZAqHlame+uEEDduDmqjTPnNKUWVeRtYKMF5Hltbeo1jMXMSbVZ+fRWKfQ+HAhQ
|
||||
xjFJowJAV6U7PqRoe0FSO1QwXrA2fHnk9nCY4qlqckZObyckAVqJhIteFPjKFNeo
|
||||
u0dAPxsPUOGGc/zwA9Sx/ZmrMuUy1QJBALl7bqawO/Ng6G0mfwZBqgeQaYYHVnnw
|
||||
E6iV353J2eHpvzNDSUFYlyEOhk4soIindSf0m9CK08Be8a+jBkocF+0CQQC+Hi7L
|
||||
kZV1slpW82BxYIhs9Gb0OQgK8SsI4aQPTFGUarQXXAm4eRqBO0kaG+jGX6TtW353
|
||||
EHK784GIxwVXKej/
|
||||
-----END PRIVATE KEY-----
|
|
@ -1 +0,0 @@
|
|||
www.bild.de
|
|
@ -1 +0,0 @@
|
|||
youtube.com
|
|
@ -0,0 +1,14 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
// see https://play-with-go.dev/tools-as-dependencies_go115_en/
|
||||
// and https://www.jvt.me/posts/2022/06/15/go-tools-dependency-management/
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/abice/go-enum"
|
||||
_ "github.com/dosgo/zigtool/zigcc"
|
||||
_ "github.com/dosgo/zigtool/zigcpp"
|
||||
_ "github.com/onsi/ginkgo/v2/ginkgo"
|
||||
_ "github.com/swaggo/swag/cmd/swag"
|
||||
)
|
|
@ -6,4 +6,6 @@ var (
|
|||
Version = "undefined"
|
||||
// BuildTime build time of the binary
|
||||
BuildTime = "undefined"
|
||||
// Architecture current CPU architecture
|
||||
Architecture = "undefined"
|
||||
)
|
||||
|
|
|
@ -3,5 +3,6 @@ package web
|
|||
import _ "embed"
|
||||
|
||||
// IndexTmpl html template for the start page
|
||||
//
|
||||
//go:embed index.html
|
||||
var IndexTmpl string
|
||||
|
|
Loading…
Reference in New Issue