mirror of https://github.com/0xERR0R/blocky.git
Compare commits
116 Commits
Author | SHA1 | Date |
---|---|---|
dependabot[bot] | aef4eca7e0 | |
dependabot[bot] | 5aa3103671 | |
Dimitri Herzog | 459bda761a | |
Dimitri Herzog | 3dcd310a9a | |
Dimitri Herzog | 5a5ba550d5 | |
Dimitri Herzog | daf3b029c8 | |
Dimitri Herzog | 1a4118b45d | |
dependabot[bot] | 4805cb60d2 | |
dependabot[bot] | 3ab04562fe | |
dependabot[bot] | d51d39929f | |
dependabot[bot] | 2476d38529 | |
dependabot[bot] | 63468a7168 | |
dependabot[bot] | 3482e93d4a | |
dependabot[bot] | bbdb80a8ad | |
dependabot[bot] | d8efa79496 | |
dependabot[bot] | 4ebe1ef21a | |
dependabot[bot] | 7f20d17d2e | |
dependabot[bot] | cbbe8d46f0 | |
dependabot[bot] | 62b1354fba | |
Thomas Anderson | e99c98b4c2 | |
dependabot[bot] | 58c5069803 | |
Kwitsch | debac9eaa8 | |
Kwitsch | 30cda6c367 | |
ThinkChaos | dbd1390589 | |
ThinkChaos | ef8c008249 | |
ThinkChaos | 90b9677198 | |
ThinkChaos | bcd1381e18 | |
dependabot[bot] | 3515483795 | |
ThinkChaos | d5b6ee93b5 | |
dependabot[bot] | 5040ed8216 | |
dependabot[bot] | 1d71bc525a | |
dependabot[bot] | 166ecbeb40 | |
dependabot[bot] | f61c93b185 | |
ThinkChaos | 9d65b9395d | |
ThinkChaos | c56f0f91ca | |
ThinkChaos | 75c2a6f9f6 | |
ThinkChaos | 1a035c3559 | |
ThinkChaos | b5682980f7 | |
ThinkChaos | 1edf8cc355 | |
ThinkChaos | 2c6b704433 | |
dependabot[bot] | 28f979fdf7 | |
dependabot[bot] | 80e7b14aad | |
dependabot[bot] | 2d49a9f455 | |
ThinkChaos | 73e5d6ab88 | |
ThinkChaos | 0a47eaad09 | |
ThinkChaos | 4919ffac0d | |
ThinkChaos | 3fcf379df7 | |
ThinkChaos | b335887992 | |
ThinkChaos | d83b7432d4 | |
dependabot[bot] | 9d50941e2f | |
dependabot[bot] | 12e5ffa6c0 | |
dependabot[bot] | 24f1187f3d | |
ThinkChaos | f5bd69cf12 | |
ThinkChaos | 5242fb68ad | |
ThinkChaos | 2ecdfd8d78 | |
dependabot[bot] | f00dbb421a | |
Kwitsch | c3a319f199 | |
Kwitsch | 7eef4bf6e2 | |
dependabot[bot] | ca7497897e | |
dependabot[bot] | 09ce2a148a | |
dependabot[bot] | 160e159113 | |
dependabot[bot] | fc490ec61c | |
dependabot[bot] | c3c229125d | |
dependabot[bot] | e5cb34ebc7 | |
dependabot[bot] | 615fd81bba | |
dependabot[bot] | 6bcc1d0606 | |
dependabot[bot] | ddbf7a3965 | |
dependabot[bot] | 4d8595f8ec | |
dependabot[bot] | d32f3b83d2 | |
dependabot[bot] | 82578d2ee5 | |
dependabot[bot] | f93d3f834f | |
dependabot[bot] | efc14d25ca | |
dependabot[bot] | 07b864e7e7 | |
dependabot[bot] | 7ce7f9ad50 | |
dependabot[bot] | ed2072071f | |
dependabot[bot] | 85ae0e70e5 | |
Dimitri Herzog | 716ad9498f | |
dependabot[bot] | e98e3432c5 | |
dependabot[bot] | db016bbdaa | |
dependabot[bot] | 29cd78071f | |
dependabot[bot] | 7de0dfe111 | |
dependabot[bot] | b7abcc303b | |
dependabot[bot] | 10e293fdb1 | |
dependabot[bot] | 8bb5b177af | |
dependabot[bot] | e26ebfc406 | |
Ben | fe84ab8ca2 | |
Ben | 9f633f18d0 | |
Ben McHone | 178dbb740e | |
dependabot[bot] | 2973045632 | |
Rahil Bhimjiani | 92b93893c7 | |
dependabot[bot] | cf5c09a3d0 | |
Ben | b8b4dc323a | |
dependabot[bot] | 3817d98e74 | |
dependabot[bot] | 5d0397d571 | |
dependabot[bot] | ac2bfd90ae | |
Shizun Ge | d2cb593d32 | |
dependabot[bot] | 3eaee7a84e | |
ThinkChaos | ad1ef0bcfb | |
ThinkChaos | dd76cf5bb0 | |
ThinkChaos | f0ad412d8d | |
ThinkChaos | e9a1e8974d | |
dependabot[bot] | aaee562460 | |
dependabot[bot] | 879087607b | |
dependabot[bot] | c8b1dd67a6 | |
Kwitsch | 2d3ad83087 | |
dependabot[bot] | 49c808f71d | |
dependabot[bot] | e686a1d39c | |
dependabot[bot] | 75a7914ec0 | |
Shizun Ge | 95bd01360b | |
ThinkChaos | 7abbaefb07 | |
ThinkChaos | 79fc06f6c2 | |
DerRockWolf | 999a16847f | |
dependabot[bot] | b302582e40 | |
dependabot[bot] | e12f6b54da | |
dependabot[bot] | 5cde62f354 | |
dependabot[bot] | 1d1206f1ea |
|
@ -0,0 +1,113 @@
|
|||
name: Build binary
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
goos:
|
||||
description: "Target OS"
|
||||
required: true
|
||||
default: "linux"
|
||||
type: choice
|
||||
options:
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
- netbsd
|
||||
- openbsd
|
||||
- darwin
|
||||
goarch:
|
||||
description: "Target architecture"
|
||||
required: true
|
||||
default: "amd64"
|
||||
type: choice
|
||||
options:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
description: "Target ARM version(only used with arm architecture)"
|
||||
required: true
|
||||
default: "7"
|
||||
type: choice
|
||||
options:
|
||||
- 6
|
||||
- 7
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Input Check
|
||||
run: |
|
||||
if [[ "${{ inputs.goos }}" == "windows" && "${{ inputs.goarch }}" == "arm" ]] || \
|
||||
[[ "${{ inputs.goos }}" == "windows" && "${{ inputs.goarch }}" == "arm64" ]] || \
|
||||
[[ "${{ inputs.goos }}" == "netbsd" && "${{ inputs.goarch }}" == "arm64" ]] || \
|
||||
[[ "${{ inputs.goos }}" == "darwin" && "${{ inputs.goarch }}" == "arm" ]]; then
|
||||
echo "## Unsupported input" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Combination not supported: ">> $GITHUB_STEP_SUMMARY
|
||||
echo " - goos=${{ inputs.goos }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo " - goarch=${{ inputs.goarch }}" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Golang
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Setup Zig
|
||||
uses: goto-bus-stop/setup-zig@v2
|
||||
|
||||
- name: Setup ZigCC & ZigCPP
|
||||
run: |
|
||||
go install github.com/dosgo/zigtool/zigcc@latest
|
||||
go install github.com/dosgo/zigtool/zigcpp@latest
|
||||
|
||||
- name: Download dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Get variables
|
||||
id: get_vars
|
||||
run: |
|
||||
# Check if the repository is forked and add upstream for correct versioning
|
||||
if [[ "${{ github.repository_owner }}" != "0xERR0R" ]]; then
|
||||
git remote add upstream https://github.com/0xERR0R/blocky.git
|
||||
git fetch upstream
|
||||
fi
|
||||
echo "version=$(git describe --always --tags)" >> $GITHUB_OUTPUT
|
||||
if [[ "${{ inputs.goarch }}" == "arm" ]]; then
|
||||
echo "arch=${{ inputs.goarch }}${{ inputs.goarm }}" >> $GITHUB_OUTPUT
|
||||
echo "arm=${{ inputs.goarm }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "arch=${{ inputs.goarch }}" >> $GITHUB_OUTPUT
|
||||
echo "arm=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GO_SKIP_GENERATE: 1
|
||||
CGO_ENABLED: 0
|
||||
CC: zigcc
|
||||
CXX: zigcpp
|
||||
GOOS: ${{ inputs.goos }}
|
||||
GOARCH: ${{ inputs.goarch }}
|
||||
GOARM: ${{ steps.get_vars.outputs.arm }}
|
||||
VERSION: ${{ steps.get_vars.outputs.version }}
|
||||
run: make build
|
||||
|
||||
- name: Rename binary
|
||||
if: inputs.goos == 'windows'
|
||||
run: mv bin/blocky bin/blocky.exe
|
||||
|
||||
- name: Store build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: blocky_${{ steps.get_vars.outputs.version }}_${{ inputs.goos }}_${{ steps.get_vars.outputs.arch }}
|
||||
path: bin/blocky*
|
||||
retention-days: 5
|
|
@ -2,11 +2,16 @@ name: docs
|
|||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags:
|
||||
- v*
|
||||
|
||||
branches:
|
||||
- '**'
|
||||
paths:
|
||||
- .github/workflows/**
|
||||
- mkdocs.yml
|
||||
- docs/**
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
|
||||
|
|
|
@ -26,7 +26,12 @@ jobs:
|
|||
else
|
||||
echo "enabled=0" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Workflow is disabled(create FORK_SYNC_TOKEN secret with repo write permission to enable it)"
|
||||
(
|
||||
echo 'Workflow is disabled (create `FORK_SYNC_TOKEN` secret with repo write permission to enable it)'
|
||||
echo
|
||||
echo 'Alternatively, you can disable it for your repo from the web UI:'
|
||||
echo 'https://docs.github.com/en/actions/using-workflows/disabling-and-enabling-a-workflow'
|
||||
) | tee "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Sync
|
||||
|
|
|
@ -2,6 +2,13 @@ name: Makefile
|
|||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/makefile.yml
|
||||
- Dockerfile
|
||||
- Makefile
|
||||
- "**.go"
|
||||
- "go.*"
|
||||
- "helpertest/data/**"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
|
@ -9,10 +16,6 @@ permissions:
|
|||
actions: read
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GINKGO_PROCS: --procs=1
|
||||
|
||||
|
@ -55,6 +58,10 @@ jobs:
|
|||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Download dependencies
|
||||
run: go mod download
|
||||
if: matrix.go == true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
if: matrix.docker == true
|
||||
|
@ -66,7 +73,7 @@ jobs:
|
|||
GO_SKIP_GENERATE: 1
|
||||
|
||||
- name: Upload results to codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
if: matrix.make == 'test' && github.repository_owner == '0xERR0R'
|
||||
|
||||
- name: Check GoReleaser configuration
|
||||
|
|
|
@ -15,3 +15,4 @@ vendor/
|
|||
coverage.html
|
||||
coverage.txt
|
||||
coverage/
|
||||
blocky
|
||||
|
|
|
@ -21,7 +21,7 @@ linters:
|
|||
- godox
|
||||
- gofmt
|
||||
- goimports
|
||||
- gomnd
|
||||
- mnd
|
||||
- gomodguard
|
||||
- gosec
|
||||
- gosimple
|
||||
|
@ -72,7 +72,7 @@ linters:
|
|||
fast: false
|
||||
|
||||
linters-settings:
|
||||
gomnd:
|
||||
mnd:
|
||||
ignored-numbers:
|
||||
- "0666"
|
||||
ginkgolinter:
|
||||
|
|
22
Dockerfile
22
Dockerfile
|
@ -2,8 +2,10 @@
|
|||
|
||||
# ----------- 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
|
||||
FROM --platform=$BUILDPLATFORM alpine:3 AS ca-certs
|
||||
RUN --mount=type=cache,target=/var/cache/apk \
|
||||
apk update && \
|
||||
apk add ca-certificates
|
||||
|
||||
# update certificates and use the apk ones if update fails
|
||||
RUN --mount=type=cache,target=/etc/ssl/certs \
|
||||
|
@ -16,17 +18,15 @@ FROM --platform=$BUILDPLATFORM ghcr.io/kwitsch/ziggoimg AS build
|
|||
ARG VERSION
|
||||
ARG BUILD_TIME
|
||||
|
||||
# set working directory
|
||||
WORKDIR /go/src
|
||||
|
||||
# download packages
|
||||
COPY go.mod go.sum ./
|
||||
RUN --mount=type=cache,target=/go/pkg \
|
||||
# bind mount go.mod and go.sum
|
||||
# use cache for go packages
|
||||
RUN --mount=type=bind,source=go.sum,target=go.sum \
|
||||
--mount=type=bind,source=go.mod,target=go.mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
go mod download
|
||||
|
||||
# add source
|
||||
COPY . .
|
||||
|
||||
# setup go
|
||||
ENV GO_SKIP_GENERATE=1\
|
||||
GO_BUILD_FLAGS="-tags static -v " \
|
||||
|
@ -35,6 +35,8 @@ ENV GO_SKIP_GENERATE=1\
|
|||
BIN_OUT_DIR="/bin"
|
||||
|
||||
# build binary
|
||||
# bind mount source code
|
||||
# use cache for go packages
|
||||
RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg \
|
||||
|
|
2
Makefile
2
Makefile
|
@ -23,7 +23,7 @@ GO_BUILD_LD_FLAGS:=\
|
|||
GO_BUILD_OUTPUT:=$(BIN_OUT_DIR)/$(BINARY_NAME)$(BINARY_SUFFIX)
|
||||
|
||||
# define version of golangci-lint here. If defined in tools.go, go mod perfoms automatically downgrade to older version which doesn't work with golang >=1.18
|
||||
GOLANG_LINT_VERSION=v1.54.2
|
||||
GOLANG_LINT_VERSION=v1.58.2
|
||||
|
||||
GINKGO_PROCS?=-p
|
||||
|
||||
|
|
|
@ -20,10 +20,10 @@ Blocky is a DNS proxy and ad-blocker for the local network written in Go with fo
|
|||
|
||||
## Features
|
||||
|
||||
- **Blocking** - Blocking of DNS queries with external lists (Ad-block, malware) and whitelisting
|
||||
- **Blocking** - Blocking of DNS queries with external lists (Ad-block, malware) and allowlisting
|
||||
|
||||
- Definition of black and white lists per client group (Kids, Smart home devices, etc.)
|
||||
- Periodical reload of external black and white lists
|
||||
- Definition of allow/denylists per client group (Kids, Smart home devices, etc.)
|
||||
- Periodical reload of external allow/denylists
|
||||
- Regex support
|
||||
- Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists)
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -17,6 +19,8 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type httpReqCtxKey struct{}
|
||||
|
||||
// BlockingStatus represents the current blocking status
|
||||
type BlockingStatus struct {
|
||||
// True if blocking is enabled
|
||||
|
@ -40,7 +44,9 @@ type ListRefresher interface {
|
|||
}
|
||||
|
||||
type Querier interface {
|
||||
Query(ctx context.Context, question string, qType dns.Type) (*model.Response, error)
|
||||
Query(
|
||||
ctx context.Context, serverHost string, clientIP net.IP, question string, qType dns.Type,
|
||||
) (*model.Response, error)
|
||||
}
|
||||
|
||||
type CacheControl interface {
|
||||
|
@ -48,7 +54,17 @@ type CacheControl interface {
|
|||
}
|
||||
|
||||
func RegisterOpenAPIEndpoints(router chi.Router, impl StrictServerInterface) {
|
||||
HandlerFromMuxWithBaseURL(NewStrictHandler(impl, nil), router, "/api")
|
||||
middleware := []StrictMiddlewareFunc{ctxWithHTTPRequestMiddleware}
|
||||
|
||||
HandlerFromMuxWithBaseURL(NewStrictHandler(impl, middleware), router, "/api")
|
||||
}
|
||||
|
||||
func ctxWithHTTPRequestMiddleware(handler StrictHandlerFunc, operationID string) StrictHandlerFunc {
|
||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, request any) (response any, err error) {
|
||||
ctx = context.WithValue(ctx, httpReqCtxKey{}, r)
|
||||
|
||||
return handler(ctx, w, r, request)
|
||||
}
|
||||
}
|
||||
|
||||
type OpenAPIInterfaceImpl struct {
|
||||
|
@ -87,7 +103,7 @@ func (i *OpenAPIInterfaceImpl) DisableBlocking(ctx context.Context,
|
|||
}
|
||||
}
|
||||
|
||||
if request.Params.Groups != nil {
|
||||
if request.Params.Groups != nil && len(*request.Params.Groups) > 0 {
|
||||
groups = strings.Split(*request.Params.Groups, ",")
|
||||
}
|
||||
|
||||
|
@ -143,7 +159,18 @@ func (i *OpenAPIInterfaceImpl) Query(ctx context.Context, request QueryRequestOb
|
|||
return Query400TextResponse(fmt.Sprintf("unknown query type '%s'", request.Body.Type)), nil
|
||||
}
|
||||
|
||||
resp, err := i.querier.Query(ctx, dns.Fqdn(request.Body.Query), qType)
|
||||
var (
|
||||
serverHost string
|
||||
clientIP net.IP
|
||||
)
|
||||
|
||||
httpReq, ok := ctx.Value(httpReqCtxKey{}).(*http.Request)
|
||||
if ok {
|
||||
serverHost = httpReq.Host
|
||||
clientIP = util.HTTPClientIP(httpReq)
|
||||
}
|
||||
|
||||
resp, err := i.querier.Query(ctx, serverHost, clientIP, dns.Fqdn(request.Body.Query), qType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,10 +3,13 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
|
@ -53,10 +56,17 @@ func (m *BlockingControlMock) BlockingStatus() BlockingStatus {
|
|||
return args.Get(0).(BlockingStatus)
|
||||
}
|
||||
|
||||
func (m *QuerierMock) Query(ctx context.Context, question string, qType dns.Type) (*model.Response, error) {
|
||||
args := m.Called(ctx, question, qType)
|
||||
func (m *QuerierMock) Query(
|
||||
ctx context.Context, serverHost string, clientIP net.IP, question string, qType dns.Type,
|
||||
) (*model.Response, error) {
|
||||
args := m.Called(ctx, serverHost, clientIP, question, qType)
|
||||
|
||||
return args.Get(0).(*model.Response), args.Error(1)
|
||||
err := args.Error(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return args.Get(0).(*model.Response), nil
|
||||
}
|
||||
|
||||
func (m *CacheControlMock) FlushCaches(ctx context.Context) {
|
||||
|
@ -92,6 +102,34 @@ var _ = Describe("API implementation tests", func() {
|
|||
listRefreshMock.AssertExpectations(GinkgoT())
|
||||
})
|
||||
|
||||
Describe("RegisterOpenAPIEndpoints", func() {
|
||||
It("adds routes", func() {
|
||||
rtr := chi.NewRouter()
|
||||
RegisterOpenAPIEndpoints(rtr, sut)
|
||||
|
||||
Expect(rtr.Routes()).ShouldNot(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ctxWithHTTPRequestMiddleware", func() {
|
||||
It("adds the request to the context", func() {
|
||||
handler := func(ctx context.Context, _ http.ResponseWriter, r *http.Request, _ any) (any, error) {
|
||||
Expect(ctx.Value(httpReqCtxKey{})).Should(BeIdenticalTo(r))
|
||||
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
handler = ctxWithHTTPRequestMiddleware(handler, "operation-id")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
resp, err := handler(ctx, nil, req, nil)
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(resp).Should(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Query API", func() {
|
||||
When("Query is called", func() {
|
||||
It("should return 200 on success", func() {
|
||||
|
@ -100,7 +138,7 @@ var _ = Describe("API implementation tests", func() {
|
|||
)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
querierMock.On("Query", ctx, "google.com.", A).Return(&model.Response{
|
||||
querierMock.On("Query", ctx, "", net.IP(nil), "google.com.", A).Return(&model.Response{
|
||||
Res: queryResponse,
|
||||
Reason: "reason",
|
||||
}, nil)
|
||||
|
@ -120,6 +158,26 @@ var _ = Describe("API implementation tests", func() {
|
|||
Expect(resp200.ReturnCode).Should(Equal("NOERROR"))
|
||||
})
|
||||
|
||||
It("extracts metadata from the HTTP request", func() {
|
||||
r, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://blocky.localhost", nil)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
clientIP := net.IPv4allrouter
|
||||
r.RemoteAddr = net.JoinHostPort(clientIP.String(), "89685")
|
||||
|
||||
ctx = context.WithValue(ctx, httpReqCtxKey{}, r)
|
||||
|
||||
expectedErr := errors.New("test")
|
||||
querierMock.On("Query", ctx, "blocky.localhost", clientIP, "example.com.", A).Return(nil, expectedErr)
|
||||
|
||||
_, err = sut.Query(ctx, QueryRequestObject{
|
||||
Body: &ApiQueryRequest{
|
||||
Query: "example.com", Type: "A",
|
||||
},
|
||||
})
|
||||
Expect(err).Should(MatchError(expectedErr))
|
||||
})
|
||||
|
||||
It("should return 400 on wrong parameter", func() {
|
||||
resp, err := sut.Query(ctx, QueryRequestObject{
|
||||
Body: &ApiQueryRequest{
|
||||
|
@ -160,6 +218,23 @@ var _ = Describe("API implementation tests", func() {
|
|||
|
||||
Describe("Control blocking status via API", func() {
|
||||
When("Disable blocking is called", func() {
|
||||
It("should return a success when receiving no groups", func() {
|
||||
var emptySlice []string
|
||||
blockingControlMock.On("DisableBlocking", 3*time.Second, emptySlice).Return(nil)
|
||||
duration := "3s"
|
||||
grroups := ""
|
||||
|
||||
resp, err := sut.DisableBlocking(ctx, DisableBlockingRequestObject{
|
||||
Params: DisableBlockingParams{
|
||||
Duration: &duration,
|
||||
Groups: &grroups,
|
||||
},
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
var resp200 DisableBlocking200Response
|
||||
Expect(resp).Should(BeAssignableToTypeOf(resp200))
|
||||
})
|
||||
|
||||
It("should return 200 on success", func() {
|
||||
blockingControlMock.On("DisableBlocking", 3*time.Second, []string{"gr1", "gr2"}).Return(nil)
|
||||
duration := "3s"
|
||||
|
|
|
@ -50,7 +50,12 @@ func (cache stringMap) contains(searchString string) bool {
|
|||
})
|
||||
|
||||
if idx < searchBucketLen {
|
||||
return cache[searchLen][idx*searchLen:idx*searchLen+searchLen] == strings.ToLower(normalized)
|
||||
blockRule := cache[searchLen][idx*searchLen : idx*searchLen+searchLen]
|
||||
if blockRule == normalized {
|
||||
log.PrefixedLog("string_map").Debugf("block rule '%s' matched with '%s'", blockRule, searchString)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -132,7 +137,7 @@ func (cache regexCache) elementCount() int {
|
|||
func (cache regexCache) contains(searchString string) bool {
|
||||
for _, regex := range cache {
|
||||
if regex.MatchString(searchString) {
|
||||
log.PrefixedLog("regexCache").Debugf("regex '%s' matched with '%s'", regex, searchString)
|
||||
log.PrefixedLog("regex_cache").Debugf("regex '%s' matched with '%s'", regex, searchString)
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -222,6 +227,7 @@ func (r *wildcardCacheFactory) addEntry(entry string) bool {
|
|||
entry = normalizeWildcard(entry)
|
||||
|
||||
r.trie.Insert(entry)
|
||||
|
||||
r.cnt++
|
||||
|
||||
return true
|
||||
|
|
|
@ -13,9 +13,10 @@ import (
|
|||
|
||||
func newBlockingCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "blocking",
|
||||
Aliases: []string{"block"},
|
||||
Short: "Control status of blocking resolver",
|
||||
Use: "blocking",
|
||||
Aliases: []string{"block"},
|
||||
Short: "Control status of blocking resolver",
|
||||
PersistentPreRunE: initConfigPreRun,
|
||||
}
|
||||
c.AddCommand(&cobra.Command{
|
||||
Use: "enable",
|
||||
|
@ -109,6 +110,7 @@ func statusBlocking(_ *cobra.Command, _ []string) error {
|
|||
if resp.JSON200.DisabledGroups != nil {
|
||||
groupNames = strings.Join(*resp.JSON200.DisabledGroups, "; ")
|
||||
}
|
||||
|
||||
if resp.JSON200.AutoEnableInSec == nil || *resp.JSON200.AutoEnableInSec == 0 {
|
||||
log.Log().Infof("blocking disabled for groups: %s", groupNames)
|
||||
} else {
|
||||
|
|
|
@ -10,8 +10,9 @@ import (
|
|||
|
||||
func newCacheCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "cache",
|
||||
Short: "Performs cache operations",
|
||||
Use: "cache",
|
||||
Short: "Performs cache operations",
|
||||
PersistentPreRunE: initConfigPreRun,
|
||||
}
|
||||
c.AddCommand(&cobra.Command{
|
||||
Use: "flush",
|
||||
|
|
|
@ -11,8 +11,9 @@ import (
|
|||
// NewListsCommand creates new command instance
|
||||
func NewListsCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "lists",
|
||||
Short: "lists operations",
|
||||
Use: "lists",
|
||||
Short: "lists operations",
|
||||
PersistentPreRunE: initConfigPreRun,
|
||||
}
|
||||
|
||||
c.AddCommand(newRefreshCommand())
|
||||
|
|
|
@ -14,10 +14,11 @@ import (
|
|||
// NewQueryCommand creates new command instance
|
||||
func NewQueryCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "query <domain>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "performs DNS query",
|
||||
RunE: query,
|
||||
Use: "query <domain>",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Short: "performs DNS query",
|
||||
RunE: query,
|
||||
PersistentPreRunE: initConfigPreRun,
|
||||
}
|
||||
|
||||
c.Flags().StringP("type", "t", "A", "query type (A, AAAA, ...)")
|
||||
|
|
21
cmd/root.go
21
cmd/root.go
|
@ -10,8 +10,6 @@ import (
|
|||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -39,6 +37,7 @@ func NewRootCommand() *cobra.Command {
|
|||
and ad-blocker for local network.
|
||||
|
||||
Complete documentation is available at https://github.com/0xERR0R/blocky`,
|
||||
PreRunE: initConfigPreRun,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return newServeCommand().RunE(cmd, args)
|
||||
},
|
||||
|
@ -56,7 +55,8 @@ Complete documentation is available at https://github.com/0xERR0R/blocky`,
|
|||
newBlockingCommand(),
|
||||
NewListsCommand(),
|
||||
NewHealthcheckCommand(),
|
||||
newCacheCommand())
|
||||
newCacheCommand(),
|
||||
NewValidateCommand())
|
||||
|
||||
return c
|
||||
}
|
||||
|
@ -65,12 +65,11 @@ func apiURL() string {
|
|||
return fmt.Sprintf("http://%s%s", net.JoinHostPort(apiHost, strconv.Itoa(int(apiPort))), "/api")
|
||||
}
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
func initConfigPreRun(cmd *cobra.Command, args []string) error {
|
||||
return initConfig()
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
func initConfig() error {
|
||||
if configPath == defaultConfigPath {
|
||||
val, present := os.LookupEnv(configFileEnvVar)
|
||||
if present {
|
||||
|
@ -85,7 +84,7 @@ func initConfig() {
|
|||
|
||||
cfg, err := config.LoadConfig(configPath, false)
|
||||
if err != nil {
|
||||
util.FatalOnError("unable to load configuration: ", err)
|
||||
return fmt.Errorf("unable to load configuration file '%s': %w", configPath, err)
|
||||
}
|
||||
|
||||
log.Configure(&cfg.Log)
|
||||
|
@ -99,13 +98,13 @@ func initConfig() {
|
|||
|
||||
port, err := config.ConvertPort(split[lastIdx])
|
||||
if err != nil {
|
||||
util.FatalOnError("can't convert port to number (1 - 65535)", err)
|
||||
|
||||
return
|
||||
return fmt.Errorf("can't convert port '%s' to number (1 - 65535): %w", split[lastIdx], err)
|
||||
}
|
||||
|
||||
apiPort = port
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute starts the command
|
||||
|
|
|
@ -39,7 +39,7 @@ var _ = Describe("root command", func() {
|
|||
" default:",
|
||||
" - 1.1.1.1",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" denylists:",
|
||||
" ads:",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
" clientGroupsBlock:",
|
||||
|
@ -53,7 +53,7 @@ var _ = Describe("root command", func() {
|
|||
os.Setenv(configFileEnvVarOld, tmpFile.Path)
|
||||
DeferCleanup(func() { os.Unsetenv(configFileEnvVarOld) })
|
||||
|
||||
initConfig()
|
||||
Expect(initConfig()).Should(Succeed())
|
||||
|
||||
Expect(configPath).Should(Equal(tmpFile.Path))
|
||||
})
|
||||
|
@ -62,7 +62,7 @@ var _ = Describe("root command", func() {
|
|||
os.Setenv(configFileEnvVar, tmpFile.Path)
|
||||
DeferCleanup(func() { os.Unsetenv(configFileEnvVar) })
|
||||
|
||||
initConfig()
|
||||
Expect(initConfig()).Should(Succeed())
|
||||
|
||||
Expect(configPath).Should(Equal(tmpFile.Path))
|
||||
})
|
||||
|
|
12
cmd/serve.go
12
cmd/serve.go
|
@ -25,10 +25,12 @@ var (
|
|||
|
||||
func newServeCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "serve",
|
||||
Args: cobra.NoArgs,
|
||||
Short: "start blocky DNS server (default command)",
|
||||
RunE: startServer,
|
||||
Use: "serve",
|
||||
Args: cobra.NoArgs,
|
||||
Short: "start blocky DNS server (default command)",
|
||||
RunE: startServer,
|
||||
PersistentPreRunE: initConfigPreRun,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +65,7 @@ func startServer(_ *cobra.Command, _ []string) error {
|
|||
select {
|
||||
case <-signals:
|
||||
log.Log().Infof("Terminating...")
|
||||
util.LogOnError("can't stop server: ", srv.Stop(ctx))
|
||||
util.LogOnError(ctx, "can't stop server: ", srv.Stop(ctx))
|
||||
done <- true
|
||||
|
||||
case err := <-errChan:
|
||||
|
|
|
@ -42,7 +42,7 @@ var _ = Describe("Serve command", func() {
|
|||
os.Setenv(configFileEnvVar, cfgFile.Path)
|
||||
DeferCleanup(func() { os.Unsetenv(configFileEnvVar) })
|
||||
|
||||
initConfig()
|
||||
Expect(initConfig()).Should(Succeed())
|
||||
})
|
||||
|
||||
errChan := make(chan error)
|
||||
|
@ -89,7 +89,7 @@ var _ = Describe("Serve command", func() {
|
|||
os.Setenv(configFileEnvVar, cfgFile.Path)
|
||||
DeferCleanup(func() { os.Unsetenv(configFileEnvVar) })
|
||||
|
||||
initConfig()
|
||||
Expect(initConfig()).Should(Succeed())
|
||||
})
|
||||
|
||||
errChan := make(chan error)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewValidateCommand creates new command instance
|
||||
func NewValidateCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "validate",
|
||||
Args: cobra.NoArgs,
|
||||
Short: "Validates the configuration",
|
||||
RunE: validateConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
func validateConfiguration(_ *cobra.Command, _ []string) error {
|
||||
log.Log().Infof("Validating configuration file: %s", configPath)
|
||||
|
||||
_, err := os.Stat(configPath)
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
return errors.New("configuration path does not exist")
|
||||
}
|
||||
|
||||
err = initConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Log().Info("Configuration is valid")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/0xERR0R/blocky/helpertest"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Validate command", func() {
|
||||
var tmpDir *helpertest.TmpFolder
|
||||
BeforeEach(func() {
|
||||
tmpDir = helpertest.NewTmpFolder("config")
|
||||
})
|
||||
When("Validate is called with not existing configuration file", func() {
|
||||
It("should terminate with error", func() {
|
||||
c := NewRootCommand()
|
||||
c.SetArgs([]string{"validate", "--config", "/notexisting/path.yaml"})
|
||||
|
||||
Expect(c.Execute()).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
When("Validate is called with existing valid configuration file", func() {
|
||||
It("should terminate without error", func() {
|
||||
cfgFile := tmpDir.CreateStringFile("config.yaml",
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
" - 1.1.1.1")
|
||||
|
||||
c := NewRootCommand()
|
||||
c.SetArgs([]string{"validate", "--config", cfgFile.Path})
|
||||
|
||||
Expect(c.Execute()).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
When("Validate is called with existing invalid configuration file", func() {
|
||||
It("should terminate with error", func() {
|
||||
cfgFile := tmpDir.CreateStringFile("config.yaml",
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
" - 1.broken file")
|
||||
|
||||
c := NewRootCommand()
|
||||
c.SetArgs([]string{"validate", "--config", cfgFile.Path})
|
||||
|
||||
Expect(c.Execute()).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
|
@ -8,8 +8,8 @@ import (
|
|||
|
||||
// Blocking configuration for query blocking
|
||||
type Blocking struct {
|
||||
BlackLists map[string][]BytesSource `yaml:"blackLists"`
|
||||
WhiteLists map[string][]BytesSource `yaml:"whiteLists"`
|
||||
Denylists map[string][]BytesSource `yaml:"denylists"`
|
||||
Allowlists map[string][]BytesSource `yaml:"allowlists"`
|
||||
ClientGroupsBlock map[string][]string `yaml:"clientGroupsBlock"`
|
||||
BlockType string `yaml:"blockType" default:"ZEROIP"`
|
||||
BlockTTL Duration `yaml:"blockTTL" default:"6h"`
|
||||
|
@ -17,19 +17,23 @@ type Blocking struct {
|
|||
|
||||
// Deprecated options
|
||||
Deprecated struct {
|
||||
DownloadTimeout *Duration `yaml:"downloadTimeout"`
|
||||
DownloadAttempts *uint `yaml:"downloadAttempts"`
|
||||
DownloadCooldown *Duration `yaml:"downloadCooldown"`
|
||||
RefreshPeriod *Duration `yaml:"refreshPeriod"`
|
||||
FailStartOnListError *bool `yaml:"failStartOnListError"`
|
||||
ProcessingConcurrency *uint `yaml:"processingConcurrency"`
|
||||
StartStrategy *InitStrategy `yaml:"startStrategy"`
|
||||
MaxErrorsPerFile *int `yaml:"maxErrorsPerFile"`
|
||||
BlackLists *map[string][]BytesSource `yaml:"blackLists"`
|
||||
WhiteLists *map[string][]BytesSource `yaml:"whiteLists"`
|
||||
DownloadTimeout *Duration `yaml:"downloadTimeout"`
|
||||
DownloadAttempts *uint `yaml:"downloadAttempts"`
|
||||
DownloadCooldown *Duration `yaml:"downloadCooldown"`
|
||||
RefreshPeriod *Duration `yaml:"refreshPeriod"`
|
||||
FailStartOnListError *bool `yaml:"failStartOnListError"`
|
||||
ProcessingConcurrency *uint `yaml:"processingConcurrency"`
|
||||
StartStrategy *InitStrategy `yaml:"startStrategy"`
|
||||
MaxErrorsPerFile *int `yaml:"maxErrorsPerFile"`
|
||||
} `yaml:",inline"`
|
||||
}
|
||||
|
||||
func (c *Blocking) migrate(logger *logrus.Entry) bool {
|
||||
return Migrate(logger, "blocking", c.Deprecated, map[string]Migrator{
|
||||
"blackLists": Move(To("denylists", c)),
|
||||
"whiteLists": Move(To("allowlists", c)),
|
||||
"downloadTimeout": Move(To("loading.downloads.timeout", &c.Loading.Downloads)),
|
||||
"downloadAttempts": Move(To("loading.downloads.attempts", &c.Loading.Downloads)),
|
||||
"downloadCooldown": Move(To("loading.downloads.cooldown", &c.Loading.Downloads)),
|
||||
|
@ -67,14 +71,14 @@ func (c *Blocking) LogConfig(logger *logrus.Entry) {
|
|||
logger.Info("loading:")
|
||||
log.WithIndent(logger, " ", c.Loading.LogConfig)
|
||||
|
||||
logger.Info("blacklist:")
|
||||
logger.Info("denylists:")
|
||||
log.WithIndent(logger, " ", func(logger *logrus.Entry) {
|
||||
c.logListGroups(logger, c.BlackLists)
|
||||
c.logListGroups(logger, c.Denylists)
|
||||
})
|
||||
|
||||
logger.Info("whitelist:")
|
||||
logger.Info("allowlists:")
|
||||
log.WithIndent(logger, " ", func(logger *logrus.Entry) {
|
||||
c.logListGroups(logger, c.WhiteLists)
|
||||
c.logListGroups(logger, c.Allowlists)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ var _ = Describe("BlockingConfig", func() {
|
|||
cfg = Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: Duration(time.Minute),
|
||||
BlackLists: map[string][]BytesSource{
|
||||
Denylists: map[string][]BytesSource{
|
||||
"gr1": NewBytesSources("/a/file/path"),
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
|
@ -60,4 +60,30 @@ var _ = Describe("BlockingConfig", func() {
|
|||
Expect(hook.Messages).Should(ContainElement(Equal("blockType = ZEROIP")))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("migrate", func() {
|
||||
It("should copy values", func() {
|
||||
cfg, err := WithDefaults[Blocking]()
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
cfg.Deprecated.BlackLists = &map[string][]BytesSource{
|
||||
"deny-group": NewBytesSources("/deny.txt"),
|
||||
}
|
||||
cfg.Deprecated.WhiteLists = &map[string][]BytesSource{
|
||||
"allow-group": NewBytesSources("/allow.txt"),
|
||||
}
|
||||
|
||||
migrated := cfg.migrate(logger)
|
||||
Expect(migrated).Should(BeTrue())
|
||||
|
||||
Expect(hook.Calls).ShouldNot(BeEmpty())
|
||||
Expect(hook.Messages).Should(ContainElements(
|
||||
ContainSubstring("blocking.allowlists"),
|
||||
ContainSubstring("blocking.denylists"),
|
||||
))
|
||||
|
||||
Expect(cfg.Allowlists).Should(Equal(*cfg.Deprecated.WhiteLists))
|
||||
Expect(cfg.Denylists).Should(Equal(*cfg.Deprecated.BlackLists))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -27,6 +27,8 @@ const (
|
|||
udpPort = 53
|
||||
tlsPort = 853
|
||||
httpsPort = 443
|
||||
|
||||
secretObfuscator = "********"
|
||||
)
|
||||
|
||||
type Configurable interface {
|
||||
|
@ -240,7 +242,7 @@ type Config struct {
|
|||
Upstream *UpstreamGroups `yaml:"upstream"`
|
||||
UpstreamTimeout *Duration `yaml:"upstreamTimeout"`
|
||||
DisableIPv6 *bool `yaml:"disableIPv6"`
|
||||
LogLevel *log.Level `yaml:"logLevel"`
|
||||
LogLevel *logrus.Level `yaml:"logLevel"`
|
||||
LogFormat *log.FormatType `yaml:"logFormat"`
|
||||
LogPrivacy *bool `yaml:"logPrivacy"`
|
||||
LogTimestamp *bool `yaml:"logTimestamp"`
|
||||
|
@ -427,6 +429,12 @@ func mustDefault[T any]() T {
|
|||
|
||||
// LoadConfig creates new config from YAML file or a directory containing YAML files
|
||||
func LoadConfig(path string, mandatory bool) (rCfg *Config, rerr error) {
|
||||
logger := logrus.NewEntry(log.Log())
|
||||
|
||||
return loadConfig(logger, path, mandatory)
|
||||
}
|
||||
|
||||
func loadConfig(logger *logrus.Entry, path string, mandatory bool) (rCfg *Config, rerr error) {
|
||||
cfg, err := WithDefaults[Config]()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -449,22 +457,31 @@ func LoadConfig(path string, mandatory bool) (rCfg *Config, rerr error) {
|
|||
return nil, fmt.Errorf("can't read config file(s): %w", err)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
var (
|
||||
data []byte
|
||||
prettyPath string
|
||||
)
|
||||
|
||||
if fs.IsDir() {
|
||||
prettyPath = filepath.Join(path, "*")
|
||||
|
||||
data, err = readFromDir(path, data)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read config files: %w", err)
|
||||
}
|
||||
} else {
|
||||
prettyPath = path
|
||||
|
||||
data, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = unmarshalConfig(data, &cfg)
|
||||
cfg.CustomDNS.Zone.configPath = prettyPath
|
||||
|
||||
err = unmarshalConfig(logger, data, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -523,14 +540,12 @@ func isRegularFile(path string) (bool, error) {
|
|||
return isRegular, nil
|
||||
}
|
||||
|
||||
func unmarshalConfig(data []byte, cfg *Config) error {
|
||||
func unmarshalConfig(logger *logrus.Entry, data []byte, cfg *Config) error {
|
||||
err := yaml.UnmarshalStrict(data, cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong file structure: %w", err)
|
||||
}
|
||||
|
||||
logger := logrus.NewEntry(log.Log())
|
||||
|
||||
usesDepredOpts := cfg.migrate(logger)
|
||||
if usesDepredOpts {
|
||||
logger.Error("configuration uses deprecated options, see warning logs for details")
|
||||
|
|
|
@ -69,10 +69,10 @@ var _ = Describe("Config", func() {
|
|||
|
||||
When("parameter 'logLevel' is set", func() {
|
||||
It("should convert to log.level", func() {
|
||||
c.Deprecated.LogLevel = ptrOf(log.LevelDebug)
|
||||
c.Deprecated.LogLevel = ptrOf(logrus.DebugLevel)
|
||||
c.migrate(logger)
|
||||
Expect(hook.Messages).Should(ContainElement(ContainSubstring("log.level")))
|
||||
Expect(c.Log.Level).Should(Equal(log.LevelDebug))
|
||||
Expect(c.Log.Level).Should(Equal(logrus.DebugLevel))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -164,6 +164,94 @@ var _ = Describe("Config", func() {
|
|||
defaultTestFileConfig(c)
|
||||
})
|
||||
})
|
||||
When("Test config file contains a zone file with $INCLUDE", func() {
|
||||
When("The config path is set to the config file", func() {
|
||||
It("Should support the $INCLUDE directive with a bare filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
cfgFile := writeConfigYmlWithLocalZoneFile(folder, "other.zone")
|
||||
|
||||
c, err = LoadConfig(cfgFile.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
It("Should support the $INCLUDE directive with a relative filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
cfgFile := writeConfigYmlWithLocalZoneFile(folder, "./other.zone")
|
||||
|
||||
c, err = LoadConfig(cfgFile.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
})
|
||||
When("The config path is set to a directory", func() {
|
||||
It("Should support the $INCLUDE directive with a bare filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
writeConfigYmlWithLocalZoneFile(folder, "other.zone")
|
||||
|
||||
c, err = LoadConfig(folder.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
It("Should support the $INCLUDE directive with a relative filename", func() {
|
||||
folder := helpertest.NewTmpFolder("zones")
|
||||
folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
writeConfigYmlWithLocalZoneFile(folder, "./other.zone")
|
||||
|
||||
c, err = LoadConfig(folder.Path, true)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.CustomDNS.Zone.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(c.CustomDNS.Zone.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
helpertest.BeDNSRecord("www.example.com.", helpertest.A, "1.2.3.4"),
|
||||
helpertest.HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
})
|
||||
})
|
||||
When("Test file does not exist", func() {
|
||||
It("should fail", func() {
|
||||
_, err := LoadConfig(tmpDir.JoinPath("config-does-not-exist.yaml"), true)
|
||||
|
@ -219,7 +307,7 @@ var _ = Describe("Config", func() {
|
|||
blocking:
|
||||
loading:
|
||||
refreshPeriod: wrongduration`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("invalid duration \"wrongduration\""))
|
||||
})
|
||||
|
@ -230,18 +318,29 @@ blocking:
|
|||
data := `customDNS:
|
||||
mapping:
|
||||
someDomain: 192.168.178.WRONG`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("invalid IP address '192.168.178.WRONG'"))
|
||||
})
|
||||
})
|
||||
When("CustomDNS hast wrong IPv6 defined", func() {
|
||||
It("should return error", func() {
|
||||
cfg := Config{}
|
||||
data := `customDNS:
|
||||
mapping:
|
||||
someDomain: 2001:MALFORMED:IP:ADDRESS:0000:8a2e:0370:7334`
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("invalid IP address '2001:MALFORMED:IP:ADDRESS:0000:8a2e:0370:7334'"))
|
||||
})
|
||||
})
|
||||
When("Conditional mapping hast wrong defined upstreams", func() {
|
||||
It("should return error", func() {
|
||||
cfg := Config{}
|
||||
data := `conditional:
|
||||
mapping:
|
||||
multiple.resolvers: 192.168.178.1,wrongprotocol:4.4.4.4:53`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("wrong host name 'wrongprotocol:4.4.4.4:53'"))
|
||||
})
|
||||
|
@ -254,7 +353,7 @@ blocking:
|
|||
- 8.8.8.8
|
||||
- wrongprotocol:8.8.4.4
|
||||
- 1.1.1.1`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("can't convert upstream 'wrongprotocol:8.8.4.4'"))
|
||||
})
|
||||
|
@ -266,7 +365,7 @@ blocking:
|
|||
queryTypes:
|
||||
- invalidqtype
|
||||
`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("unknown DNS query type: 'invalidqtype'"))
|
||||
})
|
||||
|
@ -277,7 +376,7 @@ blocking:
|
|||
cfg := Config{}
|
||||
data := "bootstrapDns: 0.0.0.0"
|
||||
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(cfg.BootstrapDNS[0].Upstream.Host).Should(Equal("0.0.0.0"))
|
||||
})
|
||||
|
@ -289,7 +388,7 @@ bootstrapDns:
|
|||
ips:
|
||||
- 0.0.0.0
|
||||
`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(cfg.BootstrapDNS[0].Upstream.Host).Should(Equal("dns.example.com"))
|
||||
Expect(cfg.BootstrapDNS[0].IPs).Should(HaveLen(1))
|
||||
|
@ -303,7 +402,7 @@ bootstrapDns:
|
|||
- 0.0.0.0
|
||||
- upstream: 1.2.3.4
|
||||
`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(cfg.BootstrapDNS).Should(HaveLen(2))
|
||||
Expect(cfg.BootstrapDNS[0].Upstream.Host).Should(Equal("dns.example.com"))
|
||||
|
@ -318,7 +417,7 @@ bootstrapDns:
|
|||
It("should return error", func() {
|
||||
cfg := Config{}
|
||||
data := `///`
|
||||
err := unmarshalConfig([]byte(data), &cfg)
|
||||
err := unmarshalConfig(logger, []byte(data), &cfg)
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("cannot unmarshal !!str `///`"))
|
||||
})
|
||||
|
@ -335,7 +434,7 @@ bootstrapDns:
|
|||
c, err = LoadConfig(tmpDir.JoinPath("config.yml"), false)
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.Log.Level).Should(Equal(log.LevelInfo))
|
||||
Expect(c.Log.Level).Should(Equal(logrus.InfoLevel))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -846,6 +945,16 @@ bootstrapDns:
|
|||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Documentation config", func() {
|
||||
It("should not use deprecated options", func() {
|
||||
logger, hook := log.NewMockEntry()
|
||||
|
||||
_, err := loadConfig(logger, "../docs/config.yml", true)
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(hook.Messages).ShouldNot(ContainElement(ContainSubstring("deprecated")))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func defaultTestFileConfig(config *Config) {
|
||||
|
@ -856,19 +965,31 @@ func defaultTestFileConfig(config *Config) {
|
|||
Expect(config.Upstreams.Groups["default"][0].Host).Should(Equal("8.8.8.8"))
|
||||
Expect(config.Upstreams.Groups["default"][1].Host).Should(Equal("8.8.4.4"))
|
||||
Expect(config.Upstreams.Groups["default"][2].Host).Should(Equal("1.1.1.1"))
|
||||
Expect(config.CustomDNS.Mapping.HostIPs).Should(HaveLen(2))
|
||||
Expect(config.CustomDNS.Mapping.HostIPs["my.duckdns.org"][0]).Should(Equal(net.ParseIP("192.168.178.3")))
|
||||
Expect(config.CustomDNS.Mapping.HostIPs["multiple.ips"][0]).Should(Equal(net.ParseIP("192.168.178.3")))
|
||||
Expect(config.CustomDNS.Mapping.HostIPs["multiple.ips"][1]).Should(Equal(net.ParseIP("192.168.178.4")))
|
||||
Expect(config.CustomDNS.Mapping.HostIPs["multiple.ips"][2]).Should(Equal(
|
||||
net.ParseIP("2001:0db8:85a3:08d3:1319:8a2e:0370:7344")))
|
||||
Expect(config.CustomDNS.Mapping).Should(HaveLen(2))
|
||||
|
||||
duckDNSEntry := config.CustomDNS.Mapping["my.duckdns.org"][0]
|
||||
duckDNSA := duckDNSEntry.(*dns.A)
|
||||
Expect(duckDNSA.A).Should(Equal(net.ParseIP("192.168.178.3")))
|
||||
|
||||
multipleIpsEntry := config.CustomDNS.Mapping["multiple.ips"][0]
|
||||
multipleIpsA := multipleIpsEntry.(*dns.A)
|
||||
Expect(multipleIpsA.A).Should(Equal(net.ParseIP("192.168.178.3")))
|
||||
|
||||
multipleIpsEntry = config.CustomDNS.Mapping["multiple.ips"][1]
|
||||
multipleIpsA = multipleIpsEntry.(*dns.A)
|
||||
Expect(multipleIpsA.A).Should(Equal(net.ParseIP("192.168.178.4")))
|
||||
|
||||
multipleIpsEntry = config.CustomDNS.Mapping["multiple.ips"][2]
|
||||
multipleIpsAAAA := multipleIpsEntry.(*dns.AAAA)
|
||||
Expect(multipleIpsAAAA.AAAA).Should(Equal(net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7344")))
|
||||
|
||||
Expect(config.Conditional.Mapping.Upstreams).Should(HaveLen(2))
|
||||
Expect(config.Conditional.Mapping.Upstreams["fritz.box"]).Should(HaveLen(1))
|
||||
Expect(config.Conditional.Mapping.Upstreams["multiple.resolvers"]).Should(HaveLen(2))
|
||||
Expect(config.ClientLookup.Upstream.Host).Should(Equal("192.168.178.1"))
|
||||
Expect(config.ClientLookup.SingleNameOrder).Should(Equal([]uint{2, 1}))
|
||||
Expect(config.Blocking.BlackLists).Should(HaveLen(2))
|
||||
Expect(config.Blocking.WhiteLists).Should(HaveLen(1))
|
||||
Expect(config.Blocking.Denylists).Should(HaveLen(2))
|
||||
Expect(config.Blocking.Allowlists).Should(HaveLen(1))
|
||||
Expect(config.Blocking.ClientGroupsBlock).Should(HaveLen(2))
|
||||
Expect(config.Blocking.BlockTTL).Should(Equal(Duration(time.Minute)))
|
||||
Expect(config.Blocking.Loading.RefreshPeriod).Should(Equal(Duration(2 * time.Hour)))
|
||||
|
@ -908,7 +1029,7 @@ func writeConfigYml(tmpDir *helpertest.TmpFolder) *helpertest.TmpFile {
|
|||
"fqdnOnly:",
|
||||
" enable: true",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" denylists:",
|
||||
" ads:",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
" - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
|
@ -918,9 +1039,9 @@ func writeConfigYml(tmpDir *helpertest.TmpFolder) *helpertest.TmpFile {
|
|||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
" special:",
|
||||
" - https://hosts-file.net/ad_servers.txt",
|
||||
" whiteLists:",
|
||||
" allowlists:",
|
||||
" ads:",
|
||||
" - whitelist.txt",
|
||||
" - allowlist.txt",
|
||||
" clientGroupsBlock:",
|
||||
" default:",
|
||||
" - ads",
|
||||
|
@ -944,6 +1065,33 @@ func writeConfigYml(tmpDir *helpertest.TmpFolder) *helpertest.TmpFile {
|
|||
)
|
||||
}
|
||||
|
||||
func writeConfigYmlWithLocalZoneFile(tmpDir *helpertest.TmpFolder, includeStr string) *helpertest.TmpFile {
|
||||
return tmpDir.CreateStringFile("config.yml",
|
||||
"upstreams:",
|
||||
" userAgent: testBlocky",
|
||||
" init:",
|
||||
" strategy: failOnError",
|
||||
" groups:",
|
||||
" default:",
|
||||
" - tcp+udp:8.8.8.8",
|
||||
" - tcp+udp:8.8.4.4",
|
||||
" - 1.1.1.1",
|
||||
"customDNS:",
|
||||
" zone: |",
|
||||
" $ORIGIN example.com.",
|
||||
" $INCLUDE "+includeStr,
|
||||
"filtering:",
|
||||
" queryTypes:",
|
||||
" - AAAA",
|
||||
" - A",
|
||||
"fqdnOnly:",
|
||||
" enable: true",
|
||||
"port: 55553,:55554,[::1]:55555",
|
||||
"logLevel: debug",
|
||||
"minTlsServeVersion: 1.3",
|
||||
)
|
||||
}
|
||||
|
||||
func writeConfigDir(tmpDir *helpertest.TmpFolder) {
|
||||
tmpDir.CreateStringFile("config1.yaml",
|
||||
"upstreams:",
|
||||
|
@ -971,7 +1119,7 @@ func writeConfigDir(tmpDir *helpertest.TmpFolder) {
|
|||
|
||||
tmpDir.CreateStringFile("config2.yaml",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" denylists:",
|
||||
" ads:",
|
||||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
|
||||
" - https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
|
||||
|
@ -981,9 +1129,9 @@ func writeConfigDir(tmpDir *helpertest.TmpFolder) {
|
|||
" - https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
|
||||
" special:",
|
||||
" - https://hosts-file.net/ad_servers.txt",
|
||||
" whiteLists:",
|
||||
" allowlists:",
|
||||
" ads:",
|
||||
" - whitelist.txt",
|
||||
" - allowlist.txt",
|
||||
" clientGroupsBlock:",
|
||||
" default:",
|
||||
" - ads",
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -13,17 +14,83 @@ type CustomDNS struct {
|
|||
RewriterConfig `yaml:",inline"`
|
||||
CustomTTL Duration `yaml:"customTTL" default:"1h"`
|
||||
Mapping CustomDNSMapping `yaml:"mapping"`
|
||||
Zone ZoneFileDNS `yaml:"zone" default:""`
|
||||
FilterUnmappedTypes bool `yaml:"filterUnmappedTypes" default:"true"`
|
||||
}
|
||||
|
||||
// CustomDNSMapping mapping for the custom DNS configuration
|
||||
type CustomDNSMapping struct {
|
||||
HostIPs map[string][]net.IP `yaml:"hostIPs"`
|
||||
type (
|
||||
CustomDNSMapping map[string]CustomDNSEntries
|
||||
CustomDNSEntries []dns.RR
|
||||
|
||||
ZoneFileDNS struct {
|
||||
RRs CustomDNSMapping
|
||||
configPath string
|
||||
}
|
||||
)
|
||||
|
||||
func (z *ZoneFileDNS) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var input string
|
||||
if err := unmarshal(&input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := make(CustomDNSMapping)
|
||||
|
||||
zoneParser := dns.NewZoneParser(strings.NewReader(input), "", z.configPath)
|
||||
zoneParser.SetIncludeAllowed(true)
|
||||
|
||||
for {
|
||||
zoneRR, ok := zoneParser.Next()
|
||||
|
||||
if !ok {
|
||||
if zoneParser.Err() != nil {
|
||||
return zoneParser.Err()
|
||||
}
|
||||
|
||||
// Done
|
||||
break
|
||||
}
|
||||
|
||||
domain := zoneRR.Header().Name
|
||||
|
||||
if _, ok := result[domain]; !ok {
|
||||
result[domain] = make(CustomDNSEntries, 0, 1)
|
||||
}
|
||||
|
||||
result[domain] = append(result[domain], zoneRR)
|
||||
}
|
||||
|
||||
z.RRs = result
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CustomDNSEntries) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var input string
|
||||
if err := unmarshal(&input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts := strings.Split(input, ",")
|
||||
result := make(CustomDNSEntries, len(parts))
|
||||
|
||||
for i, part := range parts {
|
||||
rr, err := configToRR(strings.TrimSpace(part))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result[i] = rr
|
||||
}
|
||||
|
||||
*c = result
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEnabled implements `config.Configurable`.
|
||||
func (c *CustomDNS) IsEnabled() bool {
|
||||
return len(c.Mapping.HostIPs) != 0
|
||||
return len(c.Mapping) != 0
|
||||
}
|
||||
|
||||
// LogConfig implements `config.Configurable`.
|
||||
|
@ -33,36 +100,26 @@ func (c *CustomDNS) LogConfig(logger *logrus.Entry) {
|
|||
|
||||
logger.Info("mapping:")
|
||||
|
||||
for key, val := range c.Mapping.HostIPs {
|
||||
for key, val := range c.Mapping {
|
||||
logger.Infof(" %s = %s", key, val)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements `yaml.Unmarshaler`.
|
||||
func (c *CustomDNSMapping) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var input map[string]string
|
||||
if err := unmarshal(&input); err != nil {
|
||||
return err
|
||||
func configToRR(ipStr string) (dns.RR, error) {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("invalid IP address '%s'", ipStr)
|
||||
}
|
||||
|
||||
result := make(map[string][]net.IP, len(input))
|
||||
if ip.To4() != nil {
|
||||
a := new(dns.A)
|
||||
a.A = ip
|
||||
|
||||
for k, v := range input {
|
||||
var ips []net.IP
|
||||
|
||||
for _, part := range strings.Split(v, ",") {
|
||||
ip := net.ParseIP(strings.TrimSpace(part))
|
||||
if ip == nil {
|
||||
return fmt.Errorf("invalid IP address '%s'", part)
|
||||
}
|
||||
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
|
||||
result[k] = ips
|
||||
return a, nil
|
||||
}
|
||||
|
||||
c.HostIPs = result
|
||||
aaaa := new(dns.AAAA)
|
||||
aaaa.AAAA = ip
|
||||
|
||||
return nil
|
||||
return aaaa, nil
|
||||
}
|
||||
|
|
|
@ -2,9 +2,13 @@ package config
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
. "github.com/0xERR0R/blocky/helpertest"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
@ -17,14 +21,12 @@ var _ = Describe("CustomDNSConfig", func() {
|
|||
BeforeEach(func() {
|
||||
cfg = CustomDNS{
|
||||
Mapping: CustomDNSMapping{
|
||||
HostIPs: map[string][]net.IP{
|
||||
"custom.domain": {net.ParseIP("192.168.143.123")},
|
||||
"ip6.domain": {net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
"multiple.ips": {
|
||||
net.ParseIP("192.168.143.123"),
|
||||
net.ParseIP("192.168.143.125"),
|
||||
net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
},
|
||||
"custom.domain": {&dns.A{A: net.ParseIP("192.168.143.123")}},
|
||||
"ip6.domain": {&dns.AAAA{AAAA: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")}},
|
||||
"multiple.ips": {
|
||||
&dns.A{A: net.ParseIP("192.168.143.123")},
|
||||
&dns.A{A: net.ParseIP("192.168.143.125")},
|
||||
&dns.AAAA{AAAA: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -60,27 +62,58 @@ var _ = Describe("CustomDNSConfig", func() {
|
|||
Expect(hook.Calls).ShouldNot(BeEmpty())
|
||||
Expect(hook.Messages).Should(ContainElements(
|
||||
ContainSubstring("custom.domain = "),
|
||||
ContainSubstring("ip6.domain = "),
|
||||
ContainSubstring("multiple.ips = "),
|
||||
))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("UnmarshalYAML", func() {
|
||||
Describe("CustomDNSEntries UnmarshalYAML", func() {
|
||||
It("Should parse config as map", func() {
|
||||
c := &CustomDNSMapping{}
|
||||
c := CustomDNSEntries{}
|
||||
err := c.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*map[string]string) = map[string]string{"key": "1.2.3.4"}
|
||||
*i.(*string) = "1.2.3.4"
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c.HostIPs).Should(HaveLen(1))
|
||||
Expect(c.HostIPs["key"]).Should(HaveLen(1))
|
||||
Expect(c.HostIPs["key"][0]).Should(Equal(net.ParseIP("1.2.3.4")))
|
||||
Expect(c).Should(HaveLen(1))
|
||||
|
||||
aRecord := c[0].(*dns.A)
|
||||
Expect(aRecord.A).Should(Equal(net.ParseIP("1.2.3.4")))
|
||||
})
|
||||
|
||||
It("Should parse multiple ips as comma separated string", func() {
|
||||
c := CustomDNSEntries{}
|
||||
err := c.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = "1.2.3.4,2.3.4.5"
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c).Should(HaveLen(2))
|
||||
|
||||
Expect(c[0].(*dns.A).A).Should(Equal(net.ParseIP("1.2.3.4")))
|
||||
Expect(c[1].(*dns.A).A).Should(Equal(net.ParseIP("2.3.4.5")))
|
||||
})
|
||||
|
||||
It("Should parse multiple ips as comma separated string with whitespace", func() {
|
||||
c := CustomDNSEntries{}
|
||||
err := c.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = "1.2.3.4, 2.3.4.5 , 3.4.5.6"
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(c).Should(HaveLen(3))
|
||||
|
||||
Expect(c[0].(*dns.A).A).Should(Equal(net.ParseIP("1.2.3.4")))
|
||||
Expect(c[1].(*dns.A).A).Should(Equal(net.ParseIP("2.3.4.5")))
|
||||
Expect(c[2].(*dns.A).A).Should(Equal(net.ParseIP("3.4.5.6")))
|
||||
})
|
||||
|
||||
It("should fail if wrong YAML format", func() {
|
||||
c := &CustomDNSMapping{}
|
||||
c := &CustomDNSEntries{}
|
||||
err := c.UnmarshalYAML(func(i interface{}) error {
|
||||
return errors.New("some err")
|
||||
})
|
||||
|
@ -88,4 +121,116 @@ var _ = Describe("CustomDNSConfig", func() {
|
|||
Expect(err).Should(MatchError("some err"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ZoneFileDNS UnmarshalYAML", func() {
|
||||
It("Should parse config as map", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$ORIGIN example.com.
|
||||
www 3600 A 1.2.3.4
|
||||
www 3600 AAAA 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
www6 3600 AAAA 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||||
cname 3600 CNAME www
|
||||
`)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(z.RRs).Should(HaveLen(3))
|
||||
|
||||
Expect(z.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(2),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www.example.com.", A, "1.2.3.4"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
),
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www.example.com.", AAAA, "2001:db8:85a3::8a2e:370:7334"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
))))
|
||||
|
||||
Expect(z.RRs["www6.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www6.example.com.", AAAA, "2001:db8:85a3::8a2e:370:7334"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
))))
|
||||
|
||||
Expect(z.RRs["cname.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("cname.example.com.", CNAME, "www.example.com."),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
))))
|
||||
})
|
||||
|
||||
It("Should support the $INCLUDE directive with an absolute path", func() {
|
||||
folder := NewTmpFolder("zones")
|
||||
file := folder.CreateStringFile("other.zone", "www 3600 A 1.2.3.4")
|
||||
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$ORIGIN example.com.
|
||||
$INCLUDE ` + file.Path)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(Succeed())
|
||||
Expect(z.RRs).Should(HaveLen(1))
|
||||
|
||||
Expect(z.RRs["www.example.com."]).
|
||||
Should(SatisfyAll(
|
||||
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("www.example.com.", A, "1.2.3.4"),
|
||||
HaveTTL(BeNumerically("==", 3600)),
|
||||
)),
|
||||
))
|
||||
})
|
||||
|
||||
It("Should return an error if the zone file is malformed", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$ORIGIN example.com.
|
||||
www A 1.2.3.4
|
||||
`)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("dns: missing TTL with no previous value"))
|
||||
})
|
||||
It("Should return an error if a relative record is provided without an origin", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
*i.(*string) = strings.TrimSpace(`
|
||||
$TTL 3600
|
||||
www A 1.2.3.4
|
||||
`)
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("dns: bad owner name: \"www\""))
|
||||
})
|
||||
It("Should return an error if the unmarshall function returns an error", func() {
|
||||
z := ZoneFileDNS{}
|
||||
err := z.UnmarshalYAML(func(i interface{}) error {
|
||||
return fmt.Errorf("Failed to unmarshal")
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err).Should(MatchError("Failed to unmarshal"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -102,6 +102,7 @@ func Apply[T any](dest *Dest, apply func(oldValue T)) Migrator {
|
|||
return newMigrator(dest, func(oldName string, oldValue reflect.Value) {
|
||||
valItf := oldValue.Interface()
|
||||
valTyped, ok := valItf.(T)
|
||||
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%q migration types don't match: cannot convert %v to %T", oldName, valItf, valTyped))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -13,6 +17,11 @@ type QueryLog struct {
|
|||
CreationCooldown Duration `yaml:"creationCooldown" default:"2s"`
|
||||
Fields []QueryLogField `yaml:"fields"`
|
||||
FlushInterval Duration `yaml:"flushInterval" default:"30s"`
|
||||
Ignore QueryLogIgnore `yaml:"ignore"`
|
||||
}
|
||||
|
||||
type QueryLogIgnore struct {
|
||||
SUDN bool `yaml:"sudn" default:"false"`
|
||||
}
|
||||
|
||||
// SetDefaults implements `defaults.Setter`.
|
||||
|
@ -32,7 +41,7 @@ func (c *QueryLog) LogConfig(logger *logrus.Entry) {
|
|||
logger.Infof("type: %s", c.Type)
|
||||
|
||||
if c.Target != "" {
|
||||
logger.Infof("target: %s", c.Target)
|
||||
logger.Infof("target: %s", c.censoredTarget())
|
||||
}
|
||||
|
||||
logger.Infof("logRetentionDays: %d", c.LogRetentionDays)
|
||||
|
@ -40,4 +49,29 @@ func (c *QueryLog) LogConfig(logger *logrus.Entry) {
|
|||
logger.Debugf("creationCooldown: %s", c.CreationCooldown)
|
||||
logger.Infof("flushInterval: %s", c.FlushInterval)
|
||||
logger.Infof("fields: %s", c.Fields)
|
||||
|
||||
logger.Infof("ignore:")
|
||||
log.WithIndent(logger, " ", func(e *logrus.Entry) {
|
||||
logger.Infof("sudn: %t", c.Ignore.SUDN)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *QueryLog) censoredTarget() string {
|
||||
// Make sure there's a scheme, otherwise the user is parsed as the scheme
|
||||
targetStr := c.Target
|
||||
if !strings.Contains(targetStr, "://") {
|
||||
targetStr = c.Type.String() + "://" + targetStr
|
||||
}
|
||||
|
||||
target, err := url.Parse(targetStr)
|
||||
if err != nil {
|
||||
return c.Target
|
||||
}
|
||||
|
||||
pass, ok := target.User.Password()
|
||||
if !ok {
|
||||
return c.Target
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(c.Target, pass, secretObfuscator)
|
||||
}
|
||||
|
|
|
@ -54,7 +54,23 @@ var _ = Describe("QueryLogConfig", func() {
|
|||
|
||||
Expect(hook.Calls).ShouldNot(BeEmpty())
|
||||
Expect(hook.Messages).Should(ContainElement(ContainSubstring("logRetentionDays:")))
|
||||
Expect(hook.Messages).Should(ContainElement(ContainSubstring("sudn:")))
|
||||
})
|
||||
|
||||
DescribeTable("secret censoring", func(target string) {
|
||||
cfg.Type = QueryLogTypeMysql
|
||||
cfg.Target = target
|
||||
|
||||
cfg.LogConfig(logger)
|
||||
|
||||
Expect(hook.Calls).ShouldNot(BeEmpty())
|
||||
Expect(hook.Messages).ShouldNot(ContainElement(ContainSubstring("password")))
|
||||
},
|
||||
Entry("without scheme", "user:password@localhost"),
|
||||
Entry("with scheme", "scheme://user:password@localhost"),
|
||||
Entry("no password", "localhost"),
|
||||
Entry("not a URL", "invalid!://"),
|
||||
)
|
||||
})
|
||||
|
||||
Describe("SetDefaults", func() {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -32,7 +30,7 @@ func (c *Redis) LogConfig(logger *logrus.Entry) {
|
|||
}
|
||||
|
||||
logger.Info("username: ", c.Username)
|
||||
logger.Info("password: ", obfuscatePassword(c.Password))
|
||||
logger.Info("password: ", secretObfuscator)
|
||||
logger.Info("database: ", c.Database)
|
||||
logger.Info("required: ", c.Required)
|
||||
logger.Info("connectionAttempts: ", c.ConnectionAttempts)
|
||||
|
@ -42,7 +40,7 @@ func (c *Redis) LogConfig(logger *logrus.Entry) {
|
|||
logger.Info("sentinel:")
|
||||
logger.Info(" master: ", c.Address)
|
||||
logger.Info(" username: ", c.SentinelUsername)
|
||||
logger.Info(" password: ", obfuscatePassword(c.SentinelPassword))
|
||||
logger.Info(" password: ", secretObfuscator)
|
||||
logger.Info(" addresses:")
|
||||
|
||||
for _, addr := range c.SentinelAddresses {
|
||||
|
@ -50,8 +48,3 @@ func (c *Redis) LogConfig(logger *logrus.Entry) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// obfuscatePassword replaces all characters of a password except the first and last with *
|
||||
func obfuscatePassword(pass string) string {
|
||||
return strings.Repeat("*", len(pass))
|
||||
}
|
||||
|
|
|
@ -86,19 +86,23 @@ var _ = Describe("Redis", func() {
|
|||
ContainElement(ContainSubstring(" - localhost:26380"))))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("obfuscatePassword", func() {
|
||||
When("password is empty", func() {
|
||||
It("should return empty string", func() {
|
||||
Expect(obfuscatePassword("")).Should(Equal(""))
|
||||
})
|
||||
const secretValue = "secret-value"
|
||||
|
||||
It("should not log the password", func() {
|
||||
c.Password = secretValue
|
||||
c.LogConfig(logger)
|
||||
|
||||
Expect(hook.Calls).ShouldNot(BeEmpty())
|
||||
Expect(hook.Messages).ShouldNot(ContainElement(ContainSubstring(secretValue)))
|
||||
})
|
||||
|
||||
When("password is not empty", func() {
|
||||
It("should return obfuscated password", func() {
|
||||
Expect(obfuscatePassword("test123")).Should(Equal("*******"))
|
||||
})
|
||||
It("should not log the sentinel password", func() {
|
||||
c.SentinelPassword = secretValue
|
||||
c.LogConfig(logger)
|
||||
|
||||
Expect(hook.Calls).ShouldNot(BeEmpty())
|
||||
Expect(hook.Messages).ShouldNot(ContainElement(ContainSubstring(secretValue)))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -33,7 +33,6 @@ To print runtime configuration / statistics, you can send `SIGUSR1` signal to ru
|
|||
INFO server: MEM NumGC = 1533
|
||||
INFO server: RUN NumCPU = 4
|
||||
INFO server: RUN NumGoroutine = 18
|
||||
|
||||
```
|
||||
|
||||
!!! hint
|
||||
|
@ -49,7 +48,7 @@ automatically.
|
|||
|
||||
Some links/ideas for lists:
|
||||
|
||||
### Blacklists
|
||||
### Denylists
|
||||
|
||||
* [https://github.com/StevenBlack/hosts](https://github.com/StevenBlack/hosts)
|
||||
* [https://github.com/nickspaargaren/no-google](https://github.com/nickspaargaren/no-google)
|
||||
|
@ -60,11 +59,11 @@ Some links/ideas for lists:
|
|||
|
||||
!!! warning
|
||||
|
||||
Use only blacklists from the sources you trust!
|
||||
Use only denylists from the sources you trust!
|
||||
|
||||
### Whitelists
|
||||
### Allowlists
|
||||
|
||||
* [https://github.com/anudeepND/whitelist](https://github.com/anudeepND/whitelist)
|
||||
* [https://github.com/anudeepND/whitelist](https://github.com/anudeepND/allowlist)
|
||||
|
||||
## List of public DNS servers
|
||||
|
||||
|
@ -74,7 +73,7 @@ Some links/ideas for lists:
|
|||
|
||||
Please read the description before using the DNS server as upstream. Some of them provide already an ad-blocker, some
|
||||
filters other content. If you use external DNS server with included ad-blocker, you can't choose which domains should be
|
||||
blocked, and you can't use whitelisting.
|
||||
blocked, and you can't use allowlisting.
|
||||
|
||||
This is only a small excerpt of all free available DNS servers and should only be understood as an idee.
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ info:
|
|||
|
||||
## Features
|
||||
|
||||
- **Blocking** - Blocking of DNS queries with external lists (Ad-block, malware) and whitelisting
|
||||
- **Blocking** - Blocking of DNS queries with external lists (Ad-block, malware) and allowlisting
|
||||
|
||||
- Definition of black and white lists per client group (Kids, Smart home devices, etc.)
|
||||
- Periodical reload of external black and white lists
|
||||
- Definition of allow/denylists per client group (Kids, Smart home devices, etc.)
|
||||
- Periodical reload of external allow/denylists
|
||||
- Regex support
|
||||
- Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists)
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "sum(up{job=\"blocky\"})",
|
||||
"expr": "sum(up{job=~\"$job\"})",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
|
@ -429,7 +429,7 @@
|
|||
"datasource": {
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "Number of blacklist entries",
|
||||
"description": "Number of denylist entries",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [
|
||||
|
@ -487,7 +487,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum(blocky_blacklist_cache) / sum(up{job=\"blocky\"})",
|
||||
"expr": "sum(blocky_denylist_cache) / sum(up{job=~\"$job\"})",
|
||||
"format": "table",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -495,7 +495,7 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Blacklist entries total",
|
||||
"title": "Denylist entries total",
|
||||
"transparent": true,
|
||||
"type": "stat"
|
||||
},
|
||||
|
@ -533,8 +533,8 @@
|
|||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 5
|
||||
"x": 6,
|
||||
"y": 12
|
||||
},
|
||||
"id": 28,
|
||||
"links": [],
|
||||
|
@ -561,7 +561,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum(go_memstats_sys_bytes{job=\"blocky\"})/sum(up{job=\"blocky\"})",
|
||||
"expr": "sum(go_memstats_sys_bytes{job=~\"$job\"})/sum(up{job=~\"$job\"})",
|
||||
"format": "table",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -614,7 +614,7 @@
|
|||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"x": 6,
|
||||
"y": 6
|
||||
},
|
||||
"id": 34,
|
||||
|
@ -688,7 +688,7 @@
|
|||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"x": 0,
|
||||
"y": 6
|
||||
},
|
||||
"hideTimeOverride": true,
|
||||
|
@ -778,7 +778,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum(blocky_cache_entry_count)/ sum(up{job=\"blocky\"})",
|
||||
"expr": "sum(blocky_cache_entry_count)/ sum(up{job=~\"$job\"})",
|
||||
"format": "table",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
|
@ -900,7 +900,7 @@
|
|||
"h": 3,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
"y": 12
|
||||
},
|
||||
"id": 36,
|
||||
"links": [],
|
||||
|
@ -962,7 +962,7 @@
|
|||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 53,
|
||||
|
@ -1152,8 +1152,8 @@
|
|||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 12
|
||||
"x": 18,
|
||||
"y": 5
|
||||
},
|
||||
"id": 57,
|
||||
"options": {
|
||||
|
@ -1178,7 +1178,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "sum(time() -blocky_last_list_group_refresh)/ sum(up{job=\"blocky\"})",
|
||||
"expr": "sum(time() -blocky_last_list_group_refresh)/ sum(up{job=~\"$job\"})",
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
|
@ -1214,7 +1214,7 @@
|
|||
"h": 3,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 12
|
||||
"y": 9
|
||||
},
|
||||
"id": 49,
|
||||
"options": {
|
||||
|
@ -1239,7 +1239,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum(blocky_prefetch_domain_name_cache_count)/ sum(up{job=\"blocky\"})",
|
||||
"expr": "sum(blocky_prefetch_domain_name_cache_count)/ sum(up{job=~\"$job\"})",
|
||||
"format": "table",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
|
@ -1683,7 +1683,7 @@
|
|||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": false,
|
||||
"expr": "topk(1, blocky_blacklist_cache) by (group)",
|
||||
"expr": "topk(1, blocky_denylist_cache) by (group)",
|
||||
"format": "time_series",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
|
@ -1691,7 +1691,7 @@
|
|||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Blacklist by group",
|
||||
"title": "Denylist by group",
|
||||
"transparent": true,
|
||||
"type": "piechart"
|
||||
},
|
||||
|
@ -1929,6 +1929,29 @@
|
|||
"selected": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"current": {},
|
||||
"datasource": {
|
||||
"type": "prometheus"
|
||||
},
|
||||
"definition": "label_values(blocky_blocking_enabled,job)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "job",
|
||||
"multi": false,
|
||||
"name": "job",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(blocky_blocking_enabled,job)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -243,7 +243,7 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"title": "Blocked by Blacklist",
|
||||
"title": "Blocked by Denylist",
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -243,7 +243,7 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"title": "Blocked by Blacklist",
|
||||
"title": "Blocked by Denylist",
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -62,10 +62,10 @@ conditional:
|
|||
fritz.box: 192.168.178.1
|
||||
lan.net: 192.168.178.1,192.168.178.2
|
||||
|
||||
# optional: use black and white lists to block queries (for example ads, trackers, adult pages etc.)
|
||||
# optional: use allow/denylists to block queries (for example ads, trackers, adult pages etc.)
|
||||
blocking:
|
||||
# definition of blacklist groups. Can be external link (http/https) or local file
|
||||
blackLists:
|
||||
# definition of denylist groups. Can be external link (http/https) or local file
|
||||
denylists:
|
||||
ads:
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
|
@ -77,14 +77,16 @@ blocking:
|
|||
*.example.com
|
||||
special:
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts
|
||||
# definition of whitelist groups. Attention: if the same group has black and whitelists, whitelists will be used to disable particular blacklist entries. If a group has only whitelist entries -> this means only domains from this list are allowed, all other domains will be blocked
|
||||
whiteLists:
|
||||
# definition of allowlist groups.
|
||||
# Note: if the same group has both allow/denylists, allowlists take precedence. Meaning if a domain is both blocked and allowed, it will be allowed.
|
||||
# If a group has only allowlist entries, only domains from this list are allowed, and all others be blocked.
|
||||
allowlists:
|
||||
ads:
|
||||
- whitelist.txt
|
||||
- allowlist.txt
|
||||
- |
|
||||
# inline definition with YAML literal block scalar style
|
||||
# hosts format
|
||||
whitelistdomain.com
|
||||
allowlistdomain.com
|
||||
# this is a regex
|
||||
/^banners?[_.-]/
|
||||
# definition: which groups should be applied for which client
|
||||
|
@ -242,7 +244,7 @@ minTlsServeVersion: 1.3
|
|||
#certFile: server.crt
|
||||
#keyFile: server.key
|
||||
|
||||
# optional: use these DNS servers to resolve blacklist urls and upstream DNS servers. It is useful if no system DNS resolver is configured, and/or to encrypt the bootstrap queries.
|
||||
# optional: use these DNS servers to resolve denylist urls and upstream DNS servers. It is useful if no system DNS resolver is configured, and/or to encrypt the bootstrap queries.
|
||||
bootstrapDns:
|
||||
- tcp+udp:1.1.1.1
|
||||
- https://1.1.1.1/dns-query
|
||||
|
@ -316,7 +318,7 @@ ports:
|
|||
|
||||
# optional: logging configuration
|
||||
log:
|
||||
# optional: Log level (one from debug, info, warn, error). Default: info
|
||||
# optional: Log level (one from trace, debug, info, warn, error). Default: info
|
||||
level: info
|
||||
# optional: Log format (text or json). Default: text
|
||||
format: text
|
||||
|
|
|
@ -49,12 +49,12 @@ All logging port are optional.
|
|||
|
||||
All logging options are optional.
|
||||
|
||||
| Parameter | Type | Default value | Description |
|
||||
| ------------- | ------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| log.level | enum (debug, info, warn, error) | info | Log level |
|
||||
| log.format | enum (text, json) | text | Log format (text or json). |
|
||||
| log.timestamp | bool | true | Log time stamps (true or false). |
|
||||
| log.privacy | bool | false | Obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. |
|
||||
| Parameter | Type | Default value | Description |
|
||||
| ------------- | -------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| log.level | enum (trace, debug, info, warn, error) | info | Log level |
|
||||
| log.format | enum (text, json) | text | Log format (text or json). |
|
||||
| log.timestamp | bool | true | Log timestamps (true or false). |
|
||||
| log.privacy | bool | false | Obfuscate log output (replace all alphanumeric characters with *) for user sensitive data like request domains or responses to increase privacy. |
|
||||
|
||||
!!! example
|
||||
|
||||
|
@ -81,11 +81,11 @@ This applies to all of them. The default strategy is blocking.
|
|||
|
||||
| Parameter | Type | Mandatory | Default value | Description |
|
||||
| ----------------------- | ------------------------------------ | --------- | ------------- | ---------------------------------------------- |
|
||||
| usptreams.groups | map of name to upstream | yes | | Upstream DNS servers to use, in groups. |
|
||||
| usptreams.init.strategy | enum (blocking, failOnError, fast) | no | blocking | See [Init Strategy](#init-strategy) and below. |
|
||||
| usptreams.strategy | enum (parallel_best, random, strict) | no | parallel_best | Upstream server usage strategy. |
|
||||
| usptreams.timeout | duration | no | 2s | Upstream connection timeout. |
|
||||
| usptreams.userAgent | string | no | | HTTP User Agent when connecting to upstreams. |
|
||||
| upstreams.groups | map of name to upstream | yes | | Upstream DNS servers to use, in groups. |
|
||||
| upstreams.init.strategy | enum (blocking, failOnError, fast) | no | blocking | See [Init Strategy](#init-strategy) and below. |
|
||||
| upstreams.strategy | enum (parallel_best, random, strict) | no | parallel_best | Upstream server usage strategy. |
|
||||
| upstreams.timeout | duration | no | 2s | Upstream connection timeout. |
|
||||
| upstreams.userAgent | string | no | | HTTP User Agent when connecting to upstreams. |
|
||||
|
||||
For `init.strategy`, the "init" is testing the given resolvers for each group. The potentially fatal error, depending on the strategy, is if a group has no functional resolvers.
|
||||
|
||||
|
@ -259,12 +259,13 @@ You can define your own domain name to IP mappings. For example, you can use a u
|
|||
or define a domain name for your local device on order to use the HTTPS certificate. Multiple IP addresses for one
|
||||
domain must be separated by a comma.
|
||||
|
||||
| Parameter | Type | Mandatory | Default value |
|
||||
| ------------------- | --------------------------------------- | --------- | ------------- |
|
||||
| customTTL | duration (no unit is minutes) | no | 1h |
|
||||
| rewrite | string: string (domain: domain) | no | |
|
||||
| mapping | string: string (hostname: address list) | no | |
|
||||
| filterUnmappedTypes | boolean | no | true |
|
||||
| Parameter | Type | Mandatory | Default value |
|
||||
| ------------------- | ------------------------------------------------------ | --------- | ------------- |
|
||||
| customTTL | duration used for simple mappings (no unit is minutes) | no | 1h |
|
||||
| rewrite | string: string (domain: domain) | no | |
|
||||
| mapping | string: string (hostname: address or CNAME) | no | |
|
||||
| zone | string containing a DNS Zone | no | |
|
||||
| filterUnmappedTypes | boolean | no | true |
|
||||
|
||||
!!! example
|
||||
|
||||
|
@ -278,11 +279,23 @@ domain must be separated by a comma.
|
|||
mapping:
|
||||
printer.lan: 192.168.178.3
|
||||
otherdevice.lan: 192.168.178.15,2001:0db8:85a3:08d3:1319:8a2e:0370:7344
|
||||
zone: |
|
||||
$ORIGIN example.com.
|
||||
www 3600 A 1.2.3.4
|
||||
@ 3600 CNAME www
|
||||
```
|
||||
|
||||
This configuration will also resolve any subdomain of the defined domain, recursively. For example querying any of
|
||||
`printer.lan`, `my.printer.lan` or `i.love.my.printer.lan` will return 192.168.178.3.
|
||||
|
||||
CNAME records are supported by utilizing the `zone` parameter. The zone file is a multiline string containing a [DNS Zone File](https://en.wikipedia.org/wiki/Zone_file#Example_file).
|
||||
For records defined using the `zone` parameter, the `customTTL` parameter is unused. Instead, the TTL is defined in the zone directly.
|
||||
The following directives are supported in the zone file:
|
||||
* `$ORIGIN` - sets the origin for relative domain names
|
||||
* `$TTL` - sets the default TTL for records in the zone
|
||||
* `$INCLUDE` - includes another zone file relative to the blocky executable
|
||||
* `$GENERATE` - generates a range of records
|
||||
|
||||
With the optional parameter `rewrite` you can replace domain part of the query with the defined part **before** the
|
||||
resolver lookup is performed.
|
||||
The query "printer.home" will be rewritten to "printer.lan" and return 192.168.178.3.
|
||||
|
@ -382,16 +395,16 @@ contains a map of client name and multiple IP addresses.
|
|||
|
||||
Use `192.168.178.1` for rDNS lookup. Take second name if present, if not take first name. IP address `192.168.178.29` is mapped to `laptop` as client name.
|
||||
|
||||
## Blocking and whitelisting
|
||||
## Blocking and allowlisting
|
||||
|
||||
Blocky can use lists of domains and IPs to block (e.g. advertisement, malware,
|
||||
trackers, adult sites). You can group several list sources together and define the blocking behavior per client.
|
||||
Blocking uses the [DNS sinkhole](https://en.wikipedia.org/wiki/DNS_sinkhole) approach. For each DNS query, the domain name from
|
||||
the request, IP address from the response, and any CNAME records will be checked to determine whether to block the query or not.
|
||||
|
||||
To avoid over-blocking, you can use whitelists.
|
||||
To avoid over-blocking, you can use allowlists.
|
||||
|
||||
### Definition black and whitelists
|
||||
### Definition allow/denylists
|
||||
|
||||
Lists are defined in groups. This allows using different sets of lists for different clients.
|
||||
|
||||
|
@ -408,7 +421,7 @@ The supported list formats are:
|
|||
|
||||
```yaml
|
||||
blocking:
|
||||
blackLists:
|
||||
denylists:
|
||||
ads:
|
||||
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
|
@ -423,25 +436,24 @@ The supported list formats are:
|
|||
/^banners?[_.-]/
|
||||
special:
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts
|
||||
whiteLists:
|
||||
allowlists:
|
||||
ads:
|
||||
- whitelist.txt
|
||||
- allowlist.txt
|
||||
- /path/to/file.txt
|
||||
- |
|
||||
# inline definition with YAML literal block scalar style
|
||||
whitelistdomain.com
|
||||
allowlistdomain.com
|
||||
```
|
||||
|
||||
In this example you can see 2 groups: **ads** and **special** with one list. The **ads** group includes 2 inline lists.
|
||||
|
||||
!!! warning
|
||||
|
||||
If the same group has black and whitelists, whitelists will be used to disable particular blacklist entries.
|
||||
If a group has **only** whitelist entries -> this means only domains from this list are allowed, all other domains will
|
||||
be blocked.
|
||||
If the same group has **both** allow/denylists, allowlists take precedence. Meaning if a domain is both blocked and allowed, it will be allowed.
|
||||
If a group has **only allowlist** entries, only domains from this list are allowed, and all others be blocked.
|
||||
|
||||
!!! warning
|
||||
You must also define client group mapping, otherwise you black and whitelist definition will have no effect.
|
||||
You must also define a client group mapping, otherwise the allow/denylist definitions will have no effect.
|
||||
|
||||
#### Wildcard support
|
||||
|
||||
|
@ -820,7 +832,7 @@ These settings apply only to the resolver under which they are nested.
|
|||
```yaml
|
||||
blocking:
|
||||
loading:
|
||||
# only applies to white/blacklists
|
||||
# only applies to allow/denylists
|
||||
|
||||
hostsFile:
|
||||
loading:
|
||||
|
@ -829,8 +841,8 @@ These settings apply only to the resolver under which they are nested.
|
|||
|
||||
#### Refresh / Reload
|
||||
|
||||
To keep source contents up-to-date, blocky can periodically refresh and reparse them. Default period is **
|
||||
4 hours**. You can configure this by setting the `refreshPeriod` parameter to a value in **duration format**.
|
||||
To keep source contents up-to-date, blocky can periodically refresh and reparse them. Default period is
|
||||
**4 hours**. You can configure this by setting the `refreshPeriod` parameter to a value in **duration format**.
|
||||
A value of zero or less will disable this feature.
|
||||
|
||||
!!! example
|
||||
|
|
|
@ -8,10 +8,10 @@ Blocky is a DNS proxy and ad-blocker for the local network written in Go with fo
|
|||
|
||||
## Features
|
||||
|
||||
- **Blocking** - :no_entry: Blocking of DNS queries with external lists (Ad-block, malware) and whitelisting
|
||||
- **Blocking** - :no_entry: Blocking of DNS queries with external lists (Ad-block, malware) and allowlisting
|
||||
|
||||
* Definition of black and white lists per client group (Kids, Smart home devices, etc.)
|
||||
* Periodical reload of external black and white lists
|
||||
* Definition of allow/denylists per client group (Kids, Smart home devices, etc.)
|
||||
* Periodical reload of external allow/denylists
|
||||
* Regex support
|
||||
* Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists)
|
||||
|
||||
|
|
|
@ -4,12 +4,11 @@ You can choose one of the following installation options:
|
|||
|
||||
* Run as standalone binary
|
||||
* Run as docker container
|
||||
* Kubernetes with helm chart
|
||||
|
||||
## Prepare your configuration
|
||||
|
||||
Blocky supports single or multiple YAML files as configuration. Create new `config.yaml` with your configuration (
|
||||
see [Configuration](configuration.md) for more details and all configuration options).
|
||||
Blocky supports single or multiple YAML files as configuration. Create new `config.yml` with your configuration
|
||||
(see [Configuration](configuration.md) for more details and all configuration options).
|
||||
|
||||
Simple configuration file, which enables only basic features:
|
||||
|
||||
|
@ -21,7 +20,7 @@ upstream:
|
|||
- tcp-tls:fdns1.dismail.de:853
|
||||
- https://dns.digitale-gesellschaft.ch/dns-query
|
||||
blocking:
|
||||
blackLists:
|
||||
denylists:
|
||||
ads:
|
||||
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
clientGroupsBlock:
|
||||
|
@ -39,26 +38,25 @@ run `./blocky --config config.yml`.
|
|||
|
||||
!!! warning
|
||||
|
||||
Please be aware, if you want to use port 53 or 953 on Linux you should add CAP_NET_BIND_SERVICE capability
|
||||
to the binary or run with root privileges (running as root is not recommended).
|
||||
Please be aware, if you want to use port 53 or 953 on Linux you should add `CAP_NET_BIND_SERVICE` capability
|
||||
to the binary with `setcap 'cap_net_bind_service=+ep' ./blocky`, or run as root (not recommended).
|
||||
|
||||
## Run with docker
|
||||
|
||||
### Alternative registry
|
||||
|
||||
Blocky docker images are deployed to DockerHub (`spx01/blocky`) and GitHub Container Registry (`ghcr.io/0xerr0r/blocky`)
|
||||
.
|
||||
Blocky docker images are deployed to DockerHub (`spx01/blocky`) and GitHub Container Registry (`ghcr.io/0xerr0r/blocky`).
|
||||
|
||||
### Parameters
|
||||
|
||||
You can define the location of the config file in the container with environment variable "BLOCKY_CONFIG_FILE".
|
||||
Default value is "/app/config.yml".
|
||||
You can define the location of the config file in the container with environment variable `BLOCKY_CONFIG_FILE`.
|
||||
Default value is `/app/config.yml`.
|
||||
|
||||
### Docker from command line
|
||||
|
||||
Execute following command from the command line:
|
||||
|
||||
```
|
||||
```sh
|
||||
docker run --name blocky -v /path/to/config.yml:/app/config.yml -p 4000:4000 -p 53:53/udp spx01/blocky
|
||||
```
|
||||
|
||||
|
@ -90,15 +88,15 @@ services:
|
|||
|
||||
and start docker container with
|
||||
|
||||
```
|
||||
```sh
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Advanced setup
|
||||
|
||||
Following example shows, how to run blocky in a docker container and store query logs on a SAMBA share. Local black and
|
||||
whitelists directories are mounted as volume. You can create own black or whitelists in these directories and define the
|
||||
path like '/app/whitelists/whitelist.txt' in the config file.
|
||||
allowlists directories are mounted as volume. You can create own black or allowlists in these directories and define the
|
||||
path like '/app/allowlists/allowlist.txt' in the config file.
|
||||
|
||||
!!! example
|
||||
|
||||
|
@ -112,7 +110,7 @@ services:
|
|||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
- "4000:4000/tcp" # Prometheus stats (if enabled).
|
||||
- "4000:4000/tcp" # Prometheus stats (if enabled)
|
||||
environment:
|
||||
- TZ=Europe/Berlin
|
||||
volumes:
|
||||
|
@ -120,9 +118,9 @@ services:
|
|||
- ./config.yml:/app/config.yml
|
||||
# write query logs in this volume
|
||||
- queryLogs:/logs
|
||||
# put your custom white and blacklists in these directories
|
||||
- ./blacklists:/app/blacklists/
|
||||
- ./whitelists:/app/whitelists/
|
||||
# put your custom allow/denylists in these directories
|
||||
- ./denylists:/app/denylists/
|
||||
- ./allowlists:/app/allowlists/
|
||||
|
||||
volumes:
|
||||
queryLogs:
|
||||
|
@ -137,52 +135,76 @@ volumes:
|
|||
|
||||
For complex setups, splitting the configuration between multiple YAML files might be desired. In this case, folder containing YAML files is passed on startup, Blocky will join all the files.
|
||||
|
||||
`./blocky --config ./config/`
|
||||
```sh
|
||||
./blocky --config ./config/
|
||||
```
|
||||
|
||||
!!! warning
|
||||
|
||||
Blocky simply joins the multiple YAML files. If a directive (e.g. `upstream`) is repeated in multiple files, the configuration will not load and start will fail.
|
||||
Blocky simply joins the multiple YAML files. If an option (e.g. `upstream`) is present in multiple files, the configuration will not load and start will fail.
|
||||
|
||||
## Other installation types
|
||||
|
||||
!!! warning
|
||||
|
||||
These projects are maintained by other people.
|
||||
These projects are not associated with Blocky devs and are listed here for convenience.
|
||||
|
||||
### Web UI
|
||||
|
||||
[Blocky Frontend](https://github.com/Mozart409/blocky-frontend) provides a Web UI to control blocky. See linked project for installation instructions.
|
||||
|
||||
### Run with helm chart on Kubernetes
|
||||
|
||||
See [this repo](https://github.com/truecharts/charts/tree/master/charts/enterprise/blocky),
|
||||
the [documentation](https://truecharts.org/docs/charts/enterprise/blocky/)
|
||||
and [the configuration instructions](https://truecharts.org/docs/charts/enterprise/blocky/installation-notes) for details about running blocky via helm in kubernetes.
|
||||
|
||||
### Run as an App for TrueNAS SCALE
|
||||
|
||||
You can find the App in the TrueCharts [App Catalog](https://truecharts.org/docs/manual/SCALE%20Apps/Adding-TrueCharts)
|
||||
or read the [documentation](https://truecharts.org/docs/charts/enterprise/blocky/)
|
||||
and [ configuration instructions](https://truecharts.org/docs/charts/enterprise/blocky/installation-notes) for details about running blocky as a native TrueNAS SCALE App.
|
||||
|
||||
### AUR package for Arch Linux
|
||||
### Arch Linux via AUR
|
||||
|
||||
See [https://aur.archlinux.org/packages/blocky/](https://aur.archlinux.org/packages/blocky/)
|
||||
|
||||
### Package for Alpine Linux
|
||||
### Alpine Linux
|
||||
|
||||
See [https://pkgs.alpinelinux.org/package/edge/testing/x86/blocky](https://pkgs.alpinelinux.org/package/edge/testing/x86/blocky)
|
||||
|
||||
### Installation script for CentOS/Fedora
|
||||
### CentOS/Debian/Fedora install script
|
||||
|
||||
See [https://github.com/m0zgen/blocky-installer](https://github.com/m0zgen/blocky-installer)
|
||||
|
||||
### Package for FreeBSD
|
||||
### FreeBSD
|
||||
|
||||
See [https://www.freebsd.org/cgi/ports.cgi?query=blocky&stype=all](https://www.freebsd.org/cgi/ports.cgi?query=blocky&stype=all)
|
||||
|
||||
### Homebrew package for MacOS
|
||||
### Gentoo
|
||||
|
||||
See the [Gentoo Wiki](https://wiki.gentoo.org/wiki/Project:GURU/Information_for_End_Users) to enable the GURU repository, then run `emerge net-dns/blocky`.
|
||||
|
||||
### NixOS
|
||||
|
||||
As `pkgs.blocky` and a module:
|
||||
|
||||
```nix
|
||||
services.blocky = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
# anything from config.yml
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### macOS via Homebrew
|
||||
|
||||
See [https://formulae.brew.sh/formula/blocky](https://formulae.brew.sh/formula/blocky)
|
||||
|
||||
### TrueNAS SCALE via TrueCharts
|
||||
|
||||
See [https://truecharts.org/charts/enterprise/blocky/](https://truecharts.org/charts/enterprise/blocky/)
|
||||
(TrueCharts is not an official TrueNAS project)
|
||||
|
||||
## Companion projects
|
||||
|
||||
!!! warning
|
||||
|
||||
These projects are not associated with Blocky devs and are listed here for convenience.
|
||||
|
||||
### Lists updater
|
||||
|
||||
[Blocky lists updater](https://github.com/shizunge/blocky-lists-updater) updates list related configuration without restarting blocky DNS.
|
||||
|
||||
### Web UI
|
||||
|
||||
[Blocky Frontend](https://github.com/Mozart409/blocky-frontend) provides a Web UI to control blocky.
|
||||
See linked project for installation instructions.
|
||||
|
||||
--8<-- "docs/includes/abbreviations.md"
|
||||
|
|
|
@ -27,7 +27,8 @@ To run the CLI, please ensure, that blocky DNS server is running, then execute `
|
|||
- `./blocky blocking status` to print current status of blocking
|
||||
- `./blocky query <domain>` execute DNS query (A) (simple replacement for dig, useful for debug purposes)
|
||||
- `./blocky query <domain> --type <queryType>` execute DNS query with passed query type (A, AAAA, MX, ...)
|
||||
- `./blocky lists refresh` reloads all white and blacklists
|
||||
- `./blocky lists refresh` reloads all allow/denylists
|
||||
- `./blocky validate [--config /path/to/config.yaml]` validates configuration file
|
||||
|
||||
!!! tip
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ Following metrics will be exported:
|
|||
|
||||
| name | Description |
|
||||
| ------------------------------------------------ | -------------------------------------------------------- |
|
||||
| blocky_blacklist_cache / blocky_whitelist_cache | Number of entries in blacklist/whitelist cache, partitioned by group |
|
||||
| blocky_denylist_cache / blocky_allowlist_cache | Number of entries in denylist/allowlist cache, partitioned by group |
|
||||
| blocky_error_total | Number of total queries that ended in error for any reason |
|
||||
| blocky_query_total | Number of total queries, partitioned by client and DNS request type (A, AAAA, PTR, etc) |
|
||||
| blocky_request_duration_ms_bucket | Request duration histogram, partitioned by response type (Blocked, cached, etc) |
|
||||
|
@ -25,7 +25,7 @@ Following metrics will be exported:
|
|||
### Grafana dashboard
|
||||
|
||||
Example [Grafana](https://grafana.com/) dashboard
|
||||
definition [as JSON](https://github.com/0xERR0R/blocky/blob/main/docs/blocky-grafana.json)
|
||||
definition [as JSON](blocky-grafana.json)
|
||||
or [at grafana.com](https://grafana.com/grafana/dashboards/13768)
|
||||
![grafana-dashboard](grafana-dashboard.png).
|
||||
|
||||
|
@ -45,7 +45,7 @@ blocky, prometheus (with configured scraper for blocky) and grafana with prometh
|
|||
## MySQL / MariaDB
|
||||
|
||||
If database query logging is activated (see [Query logging](configuration.md#query-logging)), you can use following
|
||||
Grafana Dashboard [as JSON](https://github.com/0xERR0R/blocky/blob/main/docs/blocky-query-grafana.json)
|
||||
Grafana Dashboard [as JSON](blocky-query-grafana.json)
|
||||
or [at grafana.com](https://grafana.com/grafana/dashboards/14980)
|
||||
|
||||
![grafana-dashboard](grafana-query-dashboard.png).
|
||||
|
@ -54,6 +54,6 @@ Please define the MySQL source in Grafana, which points to the database with blo
|
|||
|
||||
## Postgres
|
||||
|
||||
The JSON for a Grafana dashboard equivalent to the MySQL/MariaDB version is located [here](https://github.com/0xERR0R/blocky/blob/main/docs/blocky-query-grafana-postgres.json)
|
||||
The JSON for a Grafana dashboard equivalent to the MySQL/MariaDB version is located [here](blocky-query-grafana-postgres.json)
|
||||
|
||||
--8<-- "docs/includes/abbreviations.md"
|
||||
|
|
|
@ -14,18 +14,24 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Basic functional tests", func() {
|
||||
var blocky testcontainers.Container
|
||||
var err error
|
||||
var (
|
||||
e2eNet *testcontainers.DockerNetwork
|
||||
blocky testcontainers.Container
|
||||
err error
|
||||
)
|
||||
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
e2eNet = getRandomNetwork(ctx)
|
||||
})
|
||||
|
||||
Describe("Container start", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
When("wrong port configuration is provided", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
@ -50,7 +56,7 @@ var _ = Describe("Basic functional tests", func() {
|
|||
})
|
||||
When("Minimal configuration is provided", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
@ -81,7 +87,7 @@ var _ = Describe("Basic functional tests", func() {
|
|||
Context("http port configuration", func() {
|
||||
When("'httpPort' is not defined", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
@ -101,7 +107,7 @@ var _ = Describe("Basic functional tests", func() {
|
|||
})
|
||||
When("'httpPort' is defined", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
@ -137,12 +143,12 @@ var _ = Describe("Basic functional tests", func() {
|
|||
|
||||
Describe("Logging", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
When("log privacy is enabled", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
|
|
@ -11,17 +11,23 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("External lists and query blocking", func() {
|
||||
var blocky testcontainers.Container
|
||||
var err error
|
||||
var (
|
||||
e2eNet *testcontainers.DockerNetwork
|
||||
blocky testcontainers.Container
|
||||
err error
|
||||
)
|
||||
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
_, err = createDNSMokkaContainer(ctx, "moka", `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
e2eNet = getRandomNetwork(ctx)
|
||||
|
||||
_, err = createDNSMokkaContainer(ctx, "moka", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
Describe("List download on startup", func() {
|
||||
When("external blacklist ist not available", func() {
|
||||
When("external denylist ist not available", func() {
|
||||
Context("loading.strategy = blocking", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -31,7 +37,7 @@ var _ = Describe("External lists and query blocking", func() {
|
|||
"blocking:",
|
||||
" loading:",
|
||||
" strategy: blocking",
|
||||
" blackLists:",
|
||||
" denylists:",
|
||||
" ads:",
|
||||
" - http://wrong.domain.url/list.txt",
|
||||
" clientGroupsBlock:",
|
||||
|
@ -56,7 +62,7 @@ var _ = Describe("External lists and query blocking", func() {
|
|||
})
|
||||
Context("loading.strategy = failOnError", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -66,7 +72,7 @@ var _ = Describe("External lists and query blocking", func() {
|
|||
"blocking:",
|
||||
" loading:",
|
||||
" strategy: failOnError",
|
||||
" blackLists:",
|
||||
" denylists:",
|
||||
" ads:",
|
||||
" - http://wrong.domain.url/list.txt",
|
||||
" clientGroupsBlock:",
|
||||
|
@ -90,13 +96,13 @@ var _ = Describe("External lists and query blocking", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
Describe("Query blocking against external blacklists", func() {
|
||||
When("external blacklists are defined and available", func() {
|
||||
Describe("Query blocking against external denylists", func() {
|
||||
When("external denylists are defined and available", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver", tmpDir, "list.txt", "blockeddomain.com")
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver", e2eNet, "list.txt", "blockeddomain.com")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -104,7 +110,7 @@ var _ = Describe("External lists and query blocking", func() {
|
|||
" default:",
|
||||
" - moka",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" denylists:",
|
||||
" ads:",
|
||||
" - http://httpserver:8080/list.txt",
|
||||
" clientGroupsBlock:",
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/helpertest"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/avast/retry-go/v4"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/modules/mariadb"
|
||||
"github.com/testcontainers/testcontainers-go/modules/postgres"
|
||||
|
@ -27,9 +27,7 @@ import (
|
|||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var NetworkName = fmt.Sprintf("blocky-e2e-network_%d", time.Now().Unix())
|
||||
|
||||
// container image names
|
||||
const (
|
||||
redisImage = "redis:7"
|
||||
postgresImage = "postgres:15.2-alpine"
|
||||
|
@ -39,19 +37,18 @@ const (
|
|||
blockyImage = "blocky-e2e"
|
||||
)
|
||||
|
||||
func deferTerminate[T testcontainers.Container](container T, err error) (T, error) {
|
||||
ginkgo.DeferCleanup(func(ctx context.Context) error {
|
||||
if container.IsRunning() {
|
||||
return container.Terminate(ctx)
|
||||
}
|
||||
// helper constants
|
||||
const (
|
||||
modeOwner = 700
|
||||
startupTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return container, err
|
||||
}
|
||||
|
||||
func createDNSMokkaContainer(ctx context.Context, alias string, rules ...string) (testcontainers.Container, error) {
|
||||
// createDNSMokkaContainer creates a DNS mokka container with the given rules attached to the test network
|
||||
// under the given alias.
|
||||
// It is automatically terminated when the test is finished.
|
||||
func createDNSMokkaContainer(ctx context.Context, alias string, e2eNet *testcontainers.DockerNetwork,
|
||||
rules ...string,
|
||||
) (testcontainers.Container, error) {
|
||||
mokaRules := make(map[string]string)
|
||||
|
||||
for i, rule := range rules {
|
||||
|
@ -59,67 +56,56 @@ func createDNSMokkaContainer(ctx context.Context, alias string, rules ...string)
|
|||
}
|
||||
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: mokaImage,
|
||||
Networks: []string{NetworkName},
|
||||
ExposedPorts: []string{"53/tcp", "53/udp"},
|
||||
NetworkAliases: map[string][]string{NetworkName: {alias}},
|
||||
WaitingFor: wait.ForExposedPort(),
|
||||
Env: mokaRules,
|
||||
Image: mokaImage,
|
||||
ExposedPorts: []string{"53/tcp", "53/udp"},
|
||||
WaitingFor: wait.ForExposedPort(),
|
||||
Env: mokaRules,
|
||||
}
|
||||
|
||||
return deferTerminate(testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
}))
|
||||
return startContainerWithNetwork(ctx, req, alias, e2eNet)
|
||||
}
|
||||
|
||||
func createHTTPServerContainer(ctx context.Context, alias string, tmpDir *helpertest.TmpFolder,
|
||||
// createHTTPServerContainer creates a static HTTP server container that serves one file with the given lines
|
||||
// and is attached to the test network under the given alias.
|
||||
// It is automatically terminated when the test is finished.
|
||||
func createHTTPServerContainer(ctx context.Context, alias string, e2eNet *testcontainers.DockerNetwork,
|
||||
filename string, lines ...string,
|
||||
) (testcontainers.Container, error) {
|
||||
f1 := tmpDir.CreateStringFile(filename,
|
||||
lines...,
|
||||
)
|
||||
|
||||
const modeOwner = 700
|
||||
file := createTempFile(lines...)
|
||||
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: staticServerImage,
|
||||
Networks: []string{NetworkName},
|
||||
NetworkAliases: map[string][]string{NetworkName: {alias}},
|
||||
Image: staticServerImage,
|
||||
|
||||
ExposedPorts: []string{"8080/tcp"},
|
||||
Env: map[string]string{"FOLDER": "/"},
|
||||
Files: []testcontainers.ContainerFile{
|
||||
{
|
||||
HostFilePath: f1.Path,
|
||||
HostFilePath: file,
|
||||
ContainerFilePath: fmt.Sprintf("/%s", filename),
|
||||
FileMode: modeOwner,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return deferTerminate(testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
}))
|
||||
return startContainerWithNetwork(ctx, req, alias, e2eNet)
|
||||
}
|
||||
|
||||
func WithNetwork(network string) testcontainers.CustomizeRequestOption {
|
||||
return func(req *testcontainers.GenericContainerRequest) {
|
||||
req.NetworkAliases = map[string][]string{NetworkName: {network}}
|
||||
req.Networks = []string{NetworkName}
|
||||
}
|
||||
}
|
||||
|
||||
func createRedisContainer(ctx context.Context) (*redis.RedisContainer, error) {
|
||||
// createRedisContainer creates a redis container attached to the test network under the alias 'redis'.
|
||||
// It is automatically terminated when the test is finished.
|
||||
func createRedisContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
|
||||
) (*redis.RedisContainer, error) {
|
||||
return deferTerminate(redis.RunContainer(ctx,
|
||||
testcontainers.WithImage(redisImage),
|
||||
redis.WithLogLevel(redis.LogLevelVerbose),
|
||||
WithNetwork("redis"),
|
||||
withNetwork("redis", e2eNet),
|
||||
))
|
||||
}
|
||||
|
||||
func createPostgresContainer(ctx context.Context) (*postgres.PostgresContainer, error) {
|
||||
// createPostgresContainer creates a postgres container attached to the test network under the alias 'postgres'.
|
||||
// It creates a database 'user' with user 'user' and password 'user'.
|
||||
// It is automatically terminated when the test is finished.
|
||||
func createPostgresContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
|
||||
) (*postgres.PostgresContainer, error) {
|
||||
const waitLogOccurrence = 2
|
||||
|
||||
return deferTerminate(postgres.RunContainer(ctx,
|
||||
|
@ -132,46 +118,45 @@ func createPostgresContainer(ctx context.Context) (*postgres.PostgresContainer,
|
|||
wait.ForLog("database system is ready to accept connections").
|
||||
WithOccurrence(waitLogOccurrence).
|
||||
WithStartupTimeout(startupTimeout)),
|
||||
WithNetwork("postgres"),
|
||||
withNetwork("postgres", e2eNet),
|
||||
))
|
||||
}
|
||||
|
||||
func createMariaDBContainer(ctx context.Context) (*mariadb.MariaDBContainer, error) {
|
||||
// createMariaDBContainer creates a mariadb container attached to the test network under the alias 'mariaDB'.
|
||||
// It creates a database 'user' with user 'user' and password 'user'.
|
||||
// It is automatically terminated when the test is finished.
|
||||
func createMariaDBContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
|
||||
) (*mariadb.MariaDBContainer, error) {
|
||||
return deferTerminate(mariadb.RunContainer(ctx,
|
||||
testcontainers.WithImage(mariaDBImage),
|
||||
mariadb.WithDatabase("user"),
|
||||
mariadb.WithUsername("user"),
|
||||
mariadb.WithPassword("user"),
|
||||
WithNetwork("mariaDB"),
|
||||
withNetwork("mariaDB", e2eNet),
|
||||
))
|
||||
}
|
||||
|
||||
const (
|
||||
modeOwner = 700
|
||||
startupTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
func createBlockyContainer(ctx context.Context, tmpDir *helpertest.TmpFolder,
|
||||
// createBlockyContainer creates a blocky container with a config provided by the given lines.
|
||||
// It is attached to the test network under the alias 'blocky'.
|
||||
// It is automatically terminated when the test is finished.
|
||||
func createBlockyContainer(ctx context.Context, e2eNet *testcontainers.DockerNetwork,
|
||||
lines ...string,
|
||||
) (testcontainers.Container, error) {
|
||||
f1 := tmpDir.CreateStringFile("config1.yaml",
|
||||
lines...,
|
||||
)
|
||||
confFile := createTempFile(lines...)
|
||||
|
||||
cfg, err := config.LoadConfig(f1.Path, true)
|
||||
cfg, err := config.LoadConfig(confFile, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create config struct %w", err)
|
||||
}
|
||||
|
||||
req := testcontainers.ContainerRequest{
|
||||
Image: blockyImage,
|
||||
Networks: []string{NetworkName},
|
||||
Image: blockyImage,
|
||||
|
||||
ExposedPorts: []string{"53/tcp", "53/udp", "4000/tcp"},
|
||||
|
||||
Files: []testcontainers.ContainerFile{
|
||||
{
|
||||
HostFilePath: f1.Path,
|
||||
HostFilePath: confFile,
|
||||
ContainerFilePath: "/app/config.yml",
|
||||
FileMode: modeOwner,
|
||||
},
|
||||
|
@ -184,15 +169,12 @@ func createBlockyContainer(ctx context.Context, tmpDir *helpertest.TmpFolder,
|
|||
WaitingFor: wait.ForHealthCheck().WithStartupTimeout(startupTimeout),
|
||||
}
|
||||
|
||||
container, err := deferTerminate(testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
}))
|
||||
container, err := startContainerWithNetwork(ctx, req, "blocky", e2eNet)
|
||||
if err != nil {
|
||||
// attach container log if error occurs
|
||||
if r, err := container.Logs(ctx); err == nil {
|
||||
if b, err := io.ReadAll(r); err == nil {
|
||||
ginkgo.AddReportEntry("blocky container log", string(b))
|
||||
AddReportEntry("blocky container log", string(b))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +208,6 @@ func checkBlockyReadiness(ctx context.Context, cfg *config.Config, container tes
|
|||
retry.Attempts(retryAttempts),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't perform the DNS healthcheck request: %w", err)
|
||||
}
|
||||
|
@ -234,6 +215,7 @@ func checkBlockyReadiness(ctx context.Context, cfg *config.Config, container tes
|
|||
for _, httpPort := range cfg.Ports.HTTP {
|
||||
parts := strings.Split(httpPort, ":")
|
||||
port := parts[len(parts)-1]
|
||||
|
||||
err = retry.Do(
|
||||
func() error {
|
||||
return doHTTPRequest(ctx, container, port)
|
||||
|
@ -244,7 +226,6 @@ func checkBlockyReadiness(ctx context.Context, cfg *config.Config, container tes
|
|||
retry.Attempts(retryAttempts),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second))
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't perform the HTTP request: %w", err)
|
||||
}
|
||||
|
@ -279,55 +260,25 @@ func doHTTPRequest(ctx context.Context, container testcontainers.Container, cont
|
|||
return err
|
||||
}
|
||||
|
||||
func doDNSRequest(ctx context.Context, container testcontainers.Container, message *dns.Msg) (*dns.Msg, error) {
|
||||
const timeout = 5 * time.Second
|
||||
// createTempFile creates a temporary file with the given lines which is deleted after the test
|
||||
// Each created file is prefixed with 'blocky_e2e_file-'
|
||||
func createTempFile(lines ...string) string {
|
||||
file, err := os.CreateTemp("", "blocky_e2e_file-")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
c := &dns.Client{
|
||||
Net: "tcp",
|
||||
Timeout: timeout,
|
||||
}
|
||||
DeferCleanup(func() error {
|
||||
return os.Remove(file.Name())
|
||||
})
|
||||
|
||||
host, port, err := getContainerHostPort(ctx, container, "53/tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, _, err := c.Exchange(message, net.JoinHostPort(host, port))
|
||||
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func getContainerHostPort(ctx context.Context, c testcontainers.Container, p nat.Port) (host, port string, err error) {
|
||||
res, err := c.MappedPort(ctx, p)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
host, err = c.Host(ctx)
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return host, res.Port(), err
|
||||
}
|
||||
|
||||
func getContainerLogs(ctx context.Context, c testcontainers.Container) (lines []string, err error) {
|
||||
if r, err := c.Logs(ctx); err == nil {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(strings.TrimSpace(line)) > 0 {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
for i, l := range lines {
|
||||
if i != 0 {
|
||||
_, err := file.WriteString("\n")
|
||||
Expect(err).Should(Succeed())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return lines, nil
|
||||
_, err := file.WriteString(l)
|
||||
Expect(err).Should(Succeed())
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return file.Name()
|
||||
}
|
||||
|
|
|
@ -5,13 +5,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/0xERR0R/blocky/helpertest"
|
||||
"github.com/avast/retry-go/v4"
|
||||
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -23,36 +19,6 @@ func TestLists(t *testing.T) {
|
|||
RunSpecs(t, "e2e Suite", Label("e2e"))
|
||||
}
|
||||
|
||||
var (
|
||||
network testcontainers.Network
|
||||
tmpDir *helpertest.TmpFolder
|
||||
)
|
||||
|
||||
var _ = BeforeSuite(func(ctx context.Context) {
|
||||
var err error
|
||||
|
||||
network, err = testcontainers.GenericNetwork(ctx, testcontainers.GenericNetworkRequest{
|
||||
NetworkRequest: testcontainers.NetworkRequest{
|
||||
Name: NetworkName,
|
||||
CheckDuplicate: false,
|
||||
Attachable: true,
|
||||
},
|
||||
})
|
||||
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
DeferCleanup(func(ctx context.Context) {
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
return network.Remove(ctx)
|
||||
},
|
||||
retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second))
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
|
||||
tmpDir = helpertest.NewTmpFolder("config")
|
||||
|
||||
SetDefaultEventuallyTimeout(5 * time.Second)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/miekg/dns"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
testNet "github.com/testcontainers/testcontainers-go/network"
|
||||
)
|
||||
|
||||
// getRandomNetwork returns a new test network which is used for the tests and removed afterwards.
|
||||
func getRandomNetwork(ctx context.Context) *testcontainers.DockerNetwork {
|
||||
e2eNet, err := testNet.New(ctx)
|
||||
Expect(err).Should(Succeed())
|
||||
DeferCleanup(func(ctx context.Context) {
|
||||
Expect(e2eNet.Remove(ctx)).Should(Succeed())
|
||||
})
|
||||
|
||||
return e2eNet
|
||||
}
|
||||
|
||||
// deferTerminate is a helper function to terminate the container when the test is finished.
|
||||
func deferTerminate[T testcontainers.Container](container T, err error) (T, error) {
|
||||
DeferCleanup(func(ctx context.Context) error {
|
||||
if container.IsRunning() {
|
||||
return container.Terminate(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return container, err
|
||||
}
|
||||
|
||||
// startContainerWithNetwork starts the container with the given alias and attaches it to the test network.
|
||||
// The container is wrapped with deferTerminate to terminate the container when the test is finished.
|
||||
func startContainerWithNetwork(ctx context.Context, req testcontainers.ContainerRequest, alias string,
|
||||
e2eNet *testcontainers.DockerNetwork,
|
||||
) (testcontainers.Container, error) {
|
||||
greq := testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
}
|
||||
Expect(withNetwork(alias, e2eNet).Customize(&greq)).Should(Succeed())
|
||||
|
||||
return deferTerminate(testcontainers.GenericContainer(ctx, greq))
|
||||
}
|
||||
|
||||
// doDNSRequest sends the given DNS message to the container and returns the response.
|
||||
func doDNSRequest(ctx context.Context, container testcontainers.Container, message *dns.Msg) (*dns.Msg, error) {
|
||||
const timeout = 5 * time.Second
|
||||
|
||||
c := &dns.Client{
|
||||
Net: "tcp",
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
host, port, err := getContainerHostPort(ctx, container, "53/tcp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, _, err := c.Exchange(message, net.JoinHostPort(host, port))
|
||||
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// getContainerHostPort returns the host and port of the given container and port.
|
||||
func getContainerHostPort(ctx context.Context, c testcontainers.Container, p nat.Port) (host, port string, err error) {
|
||||
res, err := c.MappedPort(ctx, p)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
host, err = c.Host(ctx)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return host, res.Port(), err
|
||||
}
|
||||
|
||||
// getContainerLogs returns the logs of the given container.
|
||||
func getContainerLogs(ctx context.Context, c testcontainers.Container) (lines []string, err error) {
|
||||
if r, err := c.Logs(ctx); err == nil {
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(strings.TrimSpace(line)) > 0 {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// withNetwork returns a CustomizeRequestOption which attaches the container to the given network with the given alias.
|
||||
func withNetwork(alias string, e2eNet *testcontainers.DockerNetwork) testcontainers.CustomizeRequestOption {
|
||||
return testNet.WithNetwork([]string{alias}, e2eNet)
|
||||
}
|
|
@ -16,32 +16,40 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Metrics functional tests", func() {
|
||||
var blocky testcontainers.Container
|
||||
var err error
|
||||
var metricsURL string
|
||||
var (
|
||||
e2eNet *testcontainers.DockerNetwork
|
||||
blocky testcontainers.Container
|
||||
err error
|
||||
metricsURL string
|
||||
)
|
||||
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
e2eNet = getRandomNetwork(ctx)
|
||||
})
|
||||
|
||||
Describe("Metrics", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver1", tmpDir, "list1.txt", "domain1.com")
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver1", e2eNet, "list1.txt", "domain1.com")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver2", tmpDir, "list2.txt",
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver2", e2eNet, "list2.txt",
|
||||
"domain1.com", "domain2", "domain3")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver2", tmpDir, "list2.txt", "domain1.com", "domain2", "domain3")
|
||||
_, err = createHTTPServerContainer(ctx, "httpserver2", e2eNet, "list2.txt",
|
||||
"domain1.com", "domain2", "domain3")
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
" - moka1",
|
||||
"blocking:",
|
||||
" blackLists:",
|
||||
" denylists:",
|
||||
" group1:",
|
||||
" - http://httpserver1:8080/list1.txt",
|
||||
" group2:",
|
||||
|
@ -121,8 +129,8 @@ var _ = Describe("Metrics functional tests", func() {
|
|||
It("Should expose list cache sizes per group as metrics", func(ctx context.Context) {
|
||||
Eventually(fetchBlockyMetrics).WithArguments(ctx, metricsURL).
|
||||
Should(ContainElements(
|
||||
"blocky_blacklist_cache{group=\"group1\"} 1",
|
||||
"blocky_blacklist_cache{group=\"group2\"} 3",
|
||||
"blocky_denylist_cache{group=\"group1\"} 1",
|
||||
"blocky_denylist_cache{group=\"group2\"} 3",
|
||||
))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,23 +16,29 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Query logs functional tests", func() {
|
||||
var blocky testcontainers.Container
|
||||
var postgresDB *postgres.PostgresContainer
|
||||
var mariaDB *mariadb.MariaDBContainer
|
||||
var db *gorm.DB
|
||||
var err error
|
||||
var (
|
||||
e2eNet *testcontainers.DockerNetwork
|
||||
blocky testcontainers.Container
|
||||
postgresDB *postgres.PostgresContainer
|
||||
mariaDB *mariadb.MariaDBContainer
|
||||
db *gorm.DB
|
||||
err error
|
||||
)
|
||||
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`, `A unknown/NXDOMAIN()`)
|
||||
e2eNet = getRandomNetwork(ctx)
|
||||
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`,
|
||||
`A unknown/NXDOMAIN()`)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
|
||||
Describe("Query logging into the mariaDB database", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
mariaDB, err = createMariaDBContainer(ctx)
|
||||
mariaDB, err = createMariaDBContainer(ctx, e2eNet)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -104,10 +110,10 @@ var _ = Describe("Query logs functional tests", func() {
|
|||
|
||||
Describe("Query logging into the postgres database", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
postgresDB, err = createPostgresContainer(ctx)
|
||||
postgresDB, err = createPostgresContainer(ctx, e2eNet)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
|
|
@ -13,12 +13,17 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Redis configuration tests", func() {
|
||||
var blocky1, blocky2, mokka testcontainers.Container
|
||||
var redisClient *redis.Client
|
||||
var err error
|
||||
var (
|
||||
e2eNet *testcontainers.DockerNetwork
|
||||
blocky1, blocky2, mokka testcontainers.Container
|
||||
redisClient *redis.Client
|
||||
err error
|
||||
)
|
||||
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
redisDB, err := createRedisContainer(ctx)
|
||||
e2eNet = getRandomNetwork(ctx)
|
||||
|
||||
redisDB, err := createRedisContainer(ctx, e2eNet)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
redisConnectionString, err := redisDB.ConnectionString(ctx)
|
||||
|
@ -32,14 +37,14 @@ var _ = Describe("Redis configuration tests", func() {
|
|||
|
||||
Expect(dbSize(ctx, redisClient)).Should(BeNumerically("==", 0))
|
||||
|
||||
mokka, err = createDNSMokkaContainer(ctx, "moka1", `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
mokka, err = createDNSMokkaContainer(ctx, "moka1", e2eNet, `A google/NOERROR("A 1.2.3.4 123")`)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
|
||||
Describe("Cache sharing between blocky instances", func() {
|
||||
When("Redis and 2 blocky instances are configured", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky1, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky1, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -51,7 +56,7 @@ var _ = Describe("Redis configuration tests", func() {
|
|||
)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
blocky2, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky2, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -102,7 +107,7 @@ var _ = Describe("Redis configuration tests", func() {
|
|||
Describe("Cache loading on startup", func() {
|
||||
When("Redis and 1 blocky instance are configured", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky1, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky1, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -130,7 +135,7 @@ var _ = Describe("Redis configuration tests", func() {
|
|||
})
|
||||
|
||||
By("start other instance of blocky now -> it should load the cache from redis", func() {
|
||||
blocky2, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky2, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
|
|
@ -12,13 +12,20 @@ import (
|
|||
)
|
||||
|
||||
var _ = Describe("Upstream resolver configuration tests", func() {
|
||||
var blocky testcontainers.Container
|
||||
var err error
|
||||
var (
|
||||
e2eNet *testcontainers.DockerNetwork
|
||||
blocky testcontainers.Container
|
||||
err error
|
||||
)
|
||||
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
e2eNet = getRandomNetwork(ctx)
|
||||
})
|
||||
|
||||
Describe("'upstreams.init.strategy' parameter handling", func() {
|
||||
When("'upstreams.init.strategy' is fast and upstream server as IP is not reachable", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -39,7 +46,7 @@ var _ = Describe("Upstream resolver configuration tests", func() {
|
|||
})
|
||||
When("'upstreams.init.strategy' is fast and upstream server as host name is not reachable", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"log:",
|
||||
" level: warn",
|
||||
"upstreams:",
|
||||
|
@ -58,7 +65,7 @@ var _ = Describe("Upstream resolver configuration tests", func() {
|
|||
})
|
||||
When("'upstreams.init.strategy' is failOnError and upstream as IP address server is not reachable", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
@ -76,7 +83,7 @@ var _ = Describe("Upstream resolver configuration tests", func() {
|
|||
})
|
||||
When("'upstreams.init.strategy' is failOnError and upstream server as host name is not reachable", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
@ -95,12 +102,12 @@ var _ = Describe("Upstream resolver configuration tests", func() {
|
|||
})
|
||||
Describe("'upstreams.timeout' parameter handling", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1",
|
||||
_, err = createDNSMokkaContainer(ctx, "moka1", e2eNet,
|
||||
`A example.com/NOERROR("A 1.2.3.4 123")`,
|
||||
`A delay.com/delay(NOERROR("A 1.1.1.1 100"), "300ms")`)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
blocky, err = createBlockyContainer(ctx, tmpDir,
|
||||
blocky, err = createBlockyContainer(ctx, e2eNet,
|
||||
"upstreams:",
|
||||
" groups:",
|
||||
" default:",
|
||||
|
|
107
go.mod
107
go.mod
|
@ -1,51 +1,51 @@
|
|||
module github.com/0xERR0R/blocky
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/abice/go-enum v0.6.0
|
||||
github.com/alicebob/miniredis/v2 v2.31.0
|
||||
github.com/alicebob/miniredis/v2 v2.32.1
|
||||
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
|
||||
github.com/avast/retry-go/v4 v4.5.1
|
||||
github.com/avast/retry-go/v4 v4.6.0
|
||||
github.com/creasty/defaults v1.7.0
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/golang-lru v1.0.2
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/miekg/dns v1.1.57
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/onsi/ginkgo/v2 v2.13.2
|
||||
github.com/onsi/gomega v1.30.0
|
||||
github.com/prometheus/client_golang v1.17.0
|
||||
github.com/onsi/ginkgo/v2 v2.19.0
|
||||
github.com/onsi/gomega v1.33.1
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/net v0.25.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gorm.io/driver/mysql v1.5.2
|
||||
gorm.io/driver/postgres v1.5.4
|
||||
gorm.io/driver/sqlite v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
gorm.io/driver/mysql v1.5.6
|
||||
gorm.io/driver/postgres v1.5.7
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/gorm v1.25.10
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.1
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/ThinkChaos/parcour v0.0.0-20230710171753-fbf917c9eaef
|
||||
github.com/deepmap/oapi-codegen v1.16.2
|
||||
github.com/docker/docker v24.0.7+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/docker v26.1.3+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/dosgo/zigtool v0.0.0-20210923085854-9c6fc1d62198
|
||||
github.com/oapi-codegen/runtime v1.1.0
|
||||
github.com/testcontainers/testcontainers-go v0.26.0
|
||||
github.com/testcontainers/testcontainers-go/modules/mariadb v0.26.0
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.26.0
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.26.0
|
||||
github.com/oapi-codegen/runtime v1.1.1
|
||||
github.com/testcontainers/testcontainers-go v0.31.0
|
||||
github.com/testcontainers/testcontainers-go/modules/mariadb v0.31.0
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.31.0
|
||||
mvdan.cc/gofumpt v0.5.0
|
||||
)
|
||||
|
||||
|
@ -58,41 +58,50 @@ require (
|
|||
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/containerd/containerd v1.7.11 // indirect
|
||||
github.com/containerd/containerd v1.7.15 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/getkin/kin-openapi v0.118.0 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect
|
||||
github.com/invopop/yaml v0.1.0 // indirect
|
||||
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.4 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.4 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.9 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/tools/cmd/cover v0.1.0-deprecated // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect
|
||||
google.golang.org/grpc v1.58.3 // indirect
|
||||
)
|
||||
|
||||
|
@ -107,10 +116,9 @@ require (
|
|||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.22.3 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
|
@ -120,33 +128,32 @@ require (
|
|||
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/labstack/gommon v0.4.1 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/mattn/goveralls v0.0.12 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // 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.4.1-0.20230718164431-9a2bf3000d16 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.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.5.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/urfave/cli/v2 v2.26.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
github.com/yuin/gopher-lua v1.1.1 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.16.1
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/term v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/tools v0.21.0
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
267
go.sum
267
go.sum
|
@ -4,10 +4,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9
|
|||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.1 h1:FK6RCIUSfmbnI/imIICmboyQBkOckutaa6R5YYlLZyo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.1/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
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/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
|
@ -25,14 +23,14 @@ github.com/abice/go-enum v0.6.0 h1:J6xiV+nyu/D5c5+/rQfgkMi9zJ1Hkap8clxCZf8KNsk=
|
|||
github.com/abice/go-enum v0.6.0/go.mod h1:istq/zbgIh0kwEdbwHb+t8OS5dsB7w4w4VygV6HcpLg=
|
||||
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.31.0 h1:ObEFUNlJwoIiyjxdrYF0QIDE7qXcLc7D3WpSH4c22PU=
|
||||
github.com/alicebob/miniredis/v2 v2.31.0/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg=
|
||||
github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo=
|
||||
github.com/alicebob/miniredis/v2 v2.32.1/go.mod h1:AqkLNAfUm0K07J28hnAyyQKf/x0YkCY/g5DCtuL01Mw=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||
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.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
|
||||
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
|
||||
github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
|
||||
github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE=
|
||||
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/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||
|
@ -42,27 +40,21 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy
|
|||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/containerd v1.7.11 h1:lfGKw3eU35sjV0aG2eYZTiwFEY1pCzxdzicHP3SZILw=
|
||||
github.com/containerd/containerd v1.7.11/go.mod h1:5UluHxHTX2rdvYuZ5OJTC5m/KJNs0Zs9wVoJm9zf5ZE=
|
||||
github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
|
||||
github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
|
||||
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -70,30 +62,33 @@ github.com/deepmap/oapi-codegen v1.16.2 h1:xGHx0dNqYfy9gE8a7AVgVM8Sd5oF9SEgePzP+
|
|||
github.com/deepmap/oapi-codegen v1.16.2/go.mod h1:rdYoEA2GE+riuZ91DvpmBX9hJbQpuY9wchXpfQ3n+ho=
|
||||
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/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
|
||||
github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
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/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
|
||||
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/getkin/kin-openapi v0.118.0 h1:z43njxPmJ7TaPpMSCQb7PN0dEYno4tyBPQcrFdHoLuM=
|
||||
github.com/getkin/kin-openapi v0.118.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
|
||||
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
|
@ -106,33 +101,28 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
|
|||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d h1:um9/pc7tKMINFfP1eE7Wv6PRGXlcCSJkVajF7KJw3uQ=
|
||||
github.com/google/pprof v0.0.0-20230309165930-d61513b1440d/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
|
||||
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
|
||||
github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
|
@ -155,8 +145,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||
github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
|
||||
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
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.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
|
@ -170,15 +162,14 @@ github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCy
|
|||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/gommon v0.4.1 h1:gqEff0p/hTENGMABzezPoPSRtIh1Cvw0ueMOe0/dfOk=
|
||||
github.com/labstack/gommon v0.4.1/go.mod h1:TyTrpPqxR5KMk8LKVtLmfMjeQ5FEkBYdxLYPw/WfrOM=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
|
@ -198,23 +189,24 @@ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6
|
|||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/goveralls v0.0.12 h1:PEEeF0k1SsTjOBQ8FOmrOAoCu4ytuMaWCnWe94zxbCg=
|
||||
github.com/mattn/goveralls v0.0.12/go.mod h1:44ImGEUfmqH8bBtaMrYKsM65LXfNLWmwaxFGjZwgMSQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
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.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
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.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
|
@ -223,25 +215,20 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oapi-codegen/runtime v1.1.0 h1:rJpoNUawn5XTvekgfkvSZr0RqEnoYpFkyvrzfWeFKWM=
|
||||
github.com/oapi-codegen/runtime v1.1.0/go.mod h1:BeSfBkWWWnAnGdyS+S/GnlbmHKzf8/hwkvelJZDeKA8=
|
||||
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
|
||||
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs=
|
||||
github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw=
|
||||
github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
@ -250,32 +237,28 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
|
||||
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
|
||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
|
@ -287,27 +270,27 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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.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.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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/testcontainers/testcontainers-go v0.26.0 h1:uqcYdoOHBy1ca7gKODfBd9uTHVK3a7UL848z09MVZ0c=
|
||||
github.com/testcontainers/testcontainers-go v0.26.0/go.mod h1:ICriE9bLX5CLxL9OFQ2N+2N+f+803LNJ1utJb1+Inx0=
|
||||
github.com/testcontainers/testcontainers-go/modules/mariadb v0.26.0 h1:pfsss17K2LKwBzfuBpcV4MWdIzBPPFGl1XxLJZQX/2w=
|
||||
github.com/testcontainers/testcontainers-go/modules/mariadb v0.26.0/go.mod h1:izsSHNmRwHwjVpLFZTMg5Y/JNZYRMEkpzRDF7/tdRqk=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.26.0 h1:I5UydATCgDjdOjhKy2ztjw3EhzKgug6xsVzmJ129+wQ=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.26.0/go.mod h1:2p5a6shxPWQkSjErw6z5Sq/6DF1lMq7OnBX5R6EQrII=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.26.0 h1:GLN70++1KrLmFZWEvqqf8dnO6KzZ5ANg6lPUurR/n88=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.26.0/go.mod h1:rEFRs/2LoFtRbHO/8c78rD8S0LwOOM6Kkygw1I/zdGQ=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U=
|
||||
github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI=
|
||||
github.com/testcontainers/testcontainers-go/modules/mariadb v0.31.0 h1:njBwuZ6EpC+SdElju6KfC/iby+Fhzbp3pOjvLMql4cs=
|
||||
github.com/testcontainers/testcontainers-go/modules/mariadb v0.31.0/go.mod h1:cGmfL8QfzvV/yDQ0DTE/vDr1iFU83LPJpLqsCxM6QcY=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 h1:isAwFS3KNKRbJMbWv+wolWqOFUECmjYZ+sIRZCIBc/E=
|
||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0/go.mod h1:ZNYY8vumNCEG9YI59A9d6/YaMY49uwRhmeU563EzFGw=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.31.0 h1:5X6GhOdLwV86zcW8sxppJAMtsDC9u+r9tb3biBc9GKs=
|
||||
github.com/testcontainers/testcontainers-go/modules/redis v0.31.0/go.mod h1:dKi5xBwy1k4u8yb3saQHu7hMEJwewHXxzbcMAuLiA6o=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
|
@ -317,11 +300,8 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
|
|||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI=
|
||||
github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
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=
|
||||
|
@ -330,17 +310,33 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
|
||||
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
|
||||
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
|
@ -351,48 +347,39 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/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-20210510120138-977fb7262007/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -403,26 +390,26 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
|
@ -431,23 +418,23 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools/cmd/cover v0.1.0-deprecated h1:Rwy+mWYz6loAF+LnG1jHG/JWMHRMMC2/1XX3Ejkx9lA=
|
||||
golang.org/x/tools/cmd/cover v0.1.0-deprecated/go.mod h1:hMDiIvlpN1NoVgmjLjUJE9tMHyxHjFX7RuQ+rW12mSA=
|
||||
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||
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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
@ -462,15 +449,15 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
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.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
|
||||
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
|
||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
|
||||
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
|
||||
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
|
||||
|
|
|
@ -212,6 +212,8 @@ func (matcher *dnsRecordMatcher) matchSingle(rr dns.RR) (success bool, err error
|
|||
return v.A.String() == matcher.answer, nil
|
||||
case *dns.AAAA:
|
||||
return v.AAAA.String() == matcher.answer, nil
|
||||
case *dns.CNAME:
|
||||
return v.Target == matcher.answer, nil
|
||||
case *dns.PTR:
|
||||
return v.Ptr == matcher.answer, nil
|
||||
case *dns.MX:
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package helpertest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
)
|
||||
|
||||
type HTTPProxy struct {
|
||||
Addr net.Addr
|
||||
requestTarget atomic.Value // string: HTTP Host of latest request
|
||||
}
|
||||
|
||||
// TestHTTPProxy returns a new HTTPProxy server.
|
||||
//
|
||||
// All requests return http.StatusNotImplemented.
|
||||
func TestHTTPProxy() *HTTPProxy {
|
||||
proxyListener, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0})
|
||||
if err != nil {
|
||||
ginkgo.Fail(fmt.Sprintf("could not create HTTP proxy listener: %s", err))
|
||||
}
|
||||
|
||||
proxy := &HTTPProxy{
|
||||
Addr: proxyListener.Addr(),
|
||||
}
|
||||
|
||||
proxySrv := http.Server{ //nolint:gosec
|
||||
Addr: "127.0.0.1:0",
|
||||
Handler: proxy,
|
||||
}
|
||||
|
||||
go func() { _ = proxySrv.Serve(proxyListener) }()
|
||||
ginkgo.DeferCleanup(proxySrv.Close)
|
||||
|
||||
return proxy
|
||||
}
|
||||
|
||||
// URL returns the proxy's URL for use by clients.
|
||||
func (p *HTTPProxy) URL() *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: "http",
|
||||
Host: p.Addr.String(),
|
||||
}
|
||||
}
|
||||
|
||||
// Check ReqURL has the right type signature for http.Transport.Proxy
|
||||
var _ = http.Transport{Proxy: (*HTTPProxy)(nil).ReqURL}
|
||||
|
||||
func (p *HTTPProxy) ReqURL(*http.Request) (*url.URL, error) {
|
||||
return p.URL(), nil
|
||||
}
|
||||
|
||||
// RequestTarget returns the target of the last request.
|
||||
func (p *HTTPProxy) RequestTarget() string {
|
||||
val := p.requestTarget.Load()
|
||||
if val == nil {
|
||||
ginkgo.Fail(fmt.Sprintf("http proxy %s received no requests", p.Addr))
|
||||
}
|
||||
|
||||
return val.(string)
|
||||
}
|
||||
|
||||
func (p *HTTPProxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
p.requestTarget.Store(req.Host)
|
||||
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
|
@ -75,6 +75,7 @@ func (d *httpDownloader) DownloadFile(ctx context.Context, link string) (io.Read
|
|||
|
||||
return fmt.Errorf("got status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var netErr net.Error
|
||||
if errors.As(httpErr, &netErr) && netErr.Timeout() {
|
||||
return &TransientError{inner: netErr}
|
||||
|
|
|
@ -54,7 +54,7 @@ var _ = Describe("Downloader", func() {
|
|||
|
||||
Describe("NewDownloader", func() {
|
||||
It("Should use provided parameters", func() {
|
||||
transport := &http.Transport{}
|
||||
transport := new(http.Transport)
|
||||
|
||||
sut = NewDownloader(
|
||||
config.Downloader{
|
||||
|
@ -96,6 +96,7 @@ var _ = Describe("Downloader", func() {
|
|||
server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
DeferCleanup(server.Close)
|
||||
|
||||
sutConfig.Attempts = 3
|
||||
})
|
||||
|
@ -212,5 +213,17 @@ var _ = Describe("Downloader", func() {
|
|||
Expect(loggerHook.LastEntry().Message).Should(ContainSubstring("Name resolution err: "))
|
||||
})
|
||||
})
|
||||
When("a proxy is configured", func() {
|
||||
It("should be used", func(ctx context.Context) {
|
||||
proxy := TestHTTPProxy()
|
||||
|
||||
sut.client.Transport = &http.Transport{Proxy: proxy.ReqURL}
|
||||
|
||||
_, err := sut.DownloadFile(ctx, "http://example.com")
|
||||
Expect(err).Should(HaveOccurred())
|
||||
|
||||
Expect(proxy.RequestTarget()).Should(Equal("example.com"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -24,8 +24,8 @@ const (
|
|||
)
|
||||
|
||||
// ListCacheType represents the type of cached list ENUM(
|
||||
// blacklist // is a list with blocked domains
|
||||
// whitelist // is a list with whitelisted domains / IPs
|
||||
// denylist // is a list with blocked domains
|
||||
// allowlist // is a list with allowlisted domains / IPs
|
||||
// )
|
||||
type ListCacheType int
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ func BenchmarkRefresh(b *testing.B) {
|
|||
RefreshPeriod: config.Duration(-1),
|
||||
}
|
||||
downloader := NewDownloader(config.Downloader{}, nil)
|
||||
cache, _ := NewListCache(context.Background(), ListCacheTypeBlacklist, cfg, lists, downloader)
|
||||
cache, _ := NewListCache(context.Background(), ListCacheTypeDenylist, cfg, lists, downloader)
|
||||
|
||||
b.ReportAllocs()
|
||||
|
||||
|
|
|
@ -12,21 +12,21 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// ListCacheTypeBlacklist is a ListCacheType of type Blacklist.
|
||||
// ListCacheTypeDenylist is a ListCacheType of type Denylist.
|
||||
// is a list with blocked domains
|
||||
ListCacheTypeBlacklist ListCacheType = iota
|
||||
// ListCacheTypeWhitelist is a ListCacheType of type Whitelist.
|
||||
// is a list with whitelisted domains / IPs
|
||||
ListCacheTypeWhitelist
|
||||
ListCacheTypeDenylist ListCacheType = iota
|
||||
// ListCacheTypeAllowlist is a ListCacheType of type Allowlist.
|
||||
// is a list with allowlisted domains / IPs
|
||||
ListCacheTypeAllowlist
|
||||
)
|
||||
|
||||
var ErrInvalidListCacheType = fmt.Errorf("not a valid ListCacheType, try [%s]", strings.Join(_ListCacheTypeNames, ", "))
|
||||
|
||||
const _ListCacheTypeName = "blacklistwhitelist"
|
||||
const _ListCacheTypeName = "denylistallowlist"
|
||||
|
||||
var _ListCacheTypeNames = []string{
|
||||
_ListCacheTypeName[0:9],
|
||||
_ListCacheTypeName[9:18],
|
||||
_ListCacheTypeName[0:8],
|
||||
_ListCacheTypeName[8:17],
|
||||
}
|
||||
|
||||
// ListCacheTypeNames returns a list of possible string values of ListCacheType.
|
||||
|
@ -37,8 +37,8 @@ func ListCacheTypeNames() []string {
|
|||
}
|
||||
|
||||
var _ListCacheTypeMap = map[ListCacheType]string{
|
||||
ListCacheTypeBlacklist: _ListCacheTypeName[0:9],
|
||||
ListCacheTypeWhitelist: _ListCacheTypeName[9:18],
|
||||
ListCacheTypeDenylist: _ListCacheTypeName[0:8],
|
||||
ListCacheTypeAllowlist: _ListCacheTypeName[8:17],
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
|
@ -57,8 +57,8 @@ func (x ListCacheType) IsValid() bool {
|
|||
}
|
||||
|
||||
var _ListCacheTypeValue = map[string]ListCacheType{
|
||||
_ListCacheTypeName[0:9]: ListCacheTypeBlacklist,
|
||||
_ListCacheTypeName[9:18]: ListCacheTypeWhitelist,
|
||||
_ListCacheTypeName[0:8]: ListCacheTypeDenylist,
|
||||
_ListCacheTypeName[8:17]: ListCacheTypeAllowlist,
|
||||
}
|
||||
|
||||
// ParseListCacheType attempts to convert a string to a ListCacheType.
|
||||
|
|
|
@ -46,7 +46,7 @@ var _ = Describe("ListCache", func() {
|
|||
ctx, cancelFn = context.WithCancel(context.Background())
|
||||
DeferCleanup(cancelFn)
|
||||
|
||||
listCacheType = ListCacheTypeBlacklist
|
||||
listCacheType = ListCacheTypeDenylist
|
||||
|
||||
sutConfig, err = config.WithDefaults[config.SourceLoading]()
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -306,7 +306,7 @@ var _ = Describe("ListCache", func() {
|
|||
}
|
||||
})
|
||||
It("should match", func() {
|
||||
sut, err = NewListCache(ctx, ListCacheTypeBlacklist, sutConfig, lists, downloader)
|
||||
sut, err = NewListCache(ctx, ListCacheTypeDenylist, sutConfig, lists, downloader)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
Expect(sut.groupedCache.ElementCount("gr1")).Should(Equal(lines1 + lines2 + lines3))
|
||||
|
@ -408,7 +408,7 @@ var _ = Describe("ListCache", func() {
|
|||
})
|
||||
|
||||
It("should print list configuration", func() {
|
||||
sut, err = NewListCache(ctx, ListCacheTypeBlacklist, sutConfig, lists, downloader)
|
||||
sut, err = NewListCache(ctx, ListCacheTypeDenylist, sutConfig, lists, downloader)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
sut.LogConfig(logger)
|
||||
|
@ -432,7 +432,7 @@ var _ = Describe("ListCache", func() {
|
|||
})
|
||||
|
||||
It("should never return an error", func() {
|
||||
_, err := NewListCache(ctx, ListCacheTypeBlacklist, sutConfig, lists, downloader)
|
||||
_, err := NewListCache(ctx, ListCacheTypeDenylist, sutConfig, lists, downloader)
|
||||
Expect(err).Should(Succeed())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ctxKey struct{}
|
||||
|
||||
func NewCtx(ctx context.Context, logger *logrus.Entry) (context.Context, *logrus.Entry) {
|
||||
ctx = context.WithValue(ctx, ctxKey{}, logger)
|
||||
|
||||
return ctx, entryWithCtx(ctx, logger)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) *logrus.Entry {
|
||||
logger, ok := ctx.Value(ctxKey{}).(*logrus.Entry)
|
||||
if !ok {
|
||||
// Fallback to the global logger
|
||||
return logrus.NewEntry(Log())
|
||||
}
|
||||
|
||||
// Ensure `logger.Context == ctx`, not always the case since `ctx` could be a child of `logger.Context`
|
||||
return entryWithCtx(ctx, logger)
|
||||
}
|
||||
|
||||
func entryWithCtx(ctx context.Context, logger *logrus.Entry) *logrus.Entry {
|
||||
loggerCopy := *logger
|
||||
loggerCopy.Context = ctx
|
||||
|
||||
return &loggerCopy
|
||||
}
|
||||
|
||||
func WrapCtx(ctx context.Context, wrap func(*logrus.Entry) *logrus.Entry) (context.Context, *logrus.Entry) {
|
||||
logger := FromCtx(ctx)
|
||||
logger = wrap(logger)
|
||||
|
||||
return NewCtx(ctx, logger)
|
||||
}
|
||||
|
||||
func CtxWithFields(ctx context.Context, fields logrus.Fields) (context.Context, *logrus.Entry) {
|
||||
return WrapCtx(ctx, func(e *logrus.Entry) *logrus.Entry {
|
||||
return e.WithFields(fields)
|
||||
})
|
||||
}
|
|
@ -32,22 +32,12 @@ var (
|
|||
// )
|
||||
type FormatType int
|
||||
|
||||
// Level log level ENUM(
|
||||
// info
|
||||
// trace
|
||||
// debug
|
||||
// warn
|
||||
// error
|
||||
// fatal
|
||||
// )
|
||||
type Level int
|
||||
|
||||
// Config defines all logging configurations
|
||||
type Config struct {
|
||||
Level Level `yaml:"level" default:"info"`
|
||||
Format FormatType `yaml:"format" default:"text"`
|
||||
Privacy bool `yaml:"privacy" default:"false"`
|
||||
Timestamp bool `yaml:"timestamp" default:"true"`
|
||||
Level logrus.Level `yaml:"level" default:"info"`
|
||||
Format FormatType `yaml:"format" default:"text"`
|
||||
Privacy bool `yaml:"privacy" default:"false"`
|
||||
Timestamp bool `yaml:"timestamp" default:"true"`
|
||||
}
|
||||
|
||||
// DefaultConfig returns a new Config initialized with default values.
|
||||
|
@ -106,11 +96,7 @@ func Configure(cfg *Config) {
|
|||
|
||||
// Configure applies configuration to the given logger.
|
||||
func ConfigureLogger(logger *logrus.Logger, cfg *Config) {
|
||||
if level, err := logrus.ParseLevel(cfg.Level.String()); err != nil {
|
||||
logger.Fatalf("invalid log level %s %v", cfg.Level, err)
|
||||
} else {
|
||||
logger.SetLevel(level)
|
||||
}
|
||||
logger.SetLevel(cfg.Level)
|
||||
|
||||
switch cfg.Format {
|
||||
case FormatTypeText:
|
||||
|
|
|
@ -84,95 +84,3 @@ func (x *FormatType) UnmarshalText(text []byte) error {
|
|||
*x = tmp
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// LevelInfo is a Level of type Info.
|
||||
LevelInfo Level = iota
|
||||
// LevelTrace is a Level of type Trace.
|
||||
LevelTrace
|
||||
// LevelDebug is a Level of type Debug.
|
||||
LevelDebug
|
||||
// LevelWarn is a Level of type Warn.
|
||||
LevelWarn
|
||||
// LevelError is a Level of type Error.
|
||||
LevelError
|
||||
// LevelFatal is a Level of type Fatal.
|
||||
LevelFatal
|
||||
)
|
||||
|
||||
var ErrInvalidLevel = fmt.Errorf("not a valid Level, try [%s]", strings.Join(_LevelNames, ", "))
|
||||
|
||||
const _LevelName = "infotracedebugwarnerrorfatal"
|
||||
|
||||
var _LevelNames = []string{
|
||||
_LevelName[0:4],
|
||||
_LevelName[4:9],
|
||||
_LevelName[9:14],
|
||||
_LevelName[14:18],
|
||||
_LevelName[18:23],
|
||||
_LevelName[23:28],
|
||||
}
|
||||
|
||||
// LevelNames returns a list of possible string values of Level.
|
||||
func LevelNames() []string {
|
||||
tmp := make([]string, len(_LevelNames))
|
||||
copy(tmp, _LevelNames)
|
||||
return tmp
|
||||
}
|
||||
|
||||
var _LevelMap = map[Level]string{
|
||||
LevelInfo: _LevelName[0:4],
|
||||
LevelTrace: _LevelName[4:9],
|
||||
LevelDebug: _LevelName[9:14],
|
||||
LevelWarn: _LevelName[14:18],
|
||||
LevelError: _LevelName[18:23],
|
||||
LevelFatal: _LevelName[23:28],
|
||||
}
|
||||
|
||||
// String implements the Stringer interface.
|
||||
func (x Level) String() string {
|
||||
if str, ok := _LevelMap[x]; ok {
|
||||
return str
|
||||
}
|
||||
return fmt.Sprintf("Level(%d)", x)
|
||||
}
|
||||
|
||||
// IsValid provides a quick way to determine if the typed value is
|
||||
// part of the allowed enumerated values
|
||||
func (x Level) IsValid() bool {
|
||||
_, ok := _LevelMap[x]
|
||||
return ok
|
||||
}
|
||||
|
||||
var _LevelValue = map[string]Level{
|
||||
_LevelName[0:4]: LevelInfo,
|
||||
_LevelName[4:9]: LevelTrace,
|
||||
_LevelName[9:14]: LevelDebug,
|
||||
_LevelName[14:18]: LevelWarn,
|
||||
_LevelName[18:23]: LevelError,
|
||||
_LevelName[23:28]: LevelFatal,
|
||||
}
|
||||
|
||||
// ParseLevel attempts to convert a string to a Level.
|
||||
func ParseLevel(name string) (Level, error) {
|
||||
if x, ok := _LevelValue[name]; ok {
|
||||
return x, nil
|
||||
}
|
||||
return Level(0), fmt.Errorf("%s is %w", name, ErrInvalidLevel)
|
||||
}
|
||||
|
||||
// MarshalText implements the text marshaller method.
|
||||
func (x Level) MarshalText() ([]byte, error) {
|
||||
return []byte(x.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the text unmarshaller method.
|
||||
func (x *Level) UnmarshalText(text []byte) error {
|
||||
name := string(text)
|
||||
tmp, err := ParseLevel(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = tmp
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -28,14 +28,14 @@ func registerApplicationEventListeners() {
|
|||
}
|
||||
|
||||
func versionNumberGauge() *prometheus.GaugeVec {
|
||||
blacklistCnt := prometheus.NewGaugeVec(
|
||||
denylistCnt := prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "blocky_build_info",
|
||||
Help: "Version number and build info",
|
||||
}, []string{"version", "build_time"},
|
||||
)
|
||||
|
||||
return blacklistCnt
|
||||
return denylistCnt
|
||||
}
|
||||
|
||||
func registerBlockingEventListeners() {
|
||||
|
@ -51,23 +51,24 @@ func registerBlockingEventListeners() {
|
|||
}
|
||||
})
|
||||
|
||||
blacklistCnt := blacklistGauge()
|
||||
denylistCnt := denylistGauge()
|
||||
|
||||
whitelistCnt := whitelistGauge()
|
||||
allowlistCnt := allowlistGauge()
|
||||
|
||||
lastListGroupRefresh := lastListGroupRefresh()
|
||||
|
||||
RegisterMetric(blacklistCnt)
|
||||
RegisterMetric(whitelistCnt)
|
||||
RegisterMetric(denylistCnt)
|
||||
RegisterMetric(allowlistCnt)
|
||||
RegisterMetric(lastListGroupRefresh)
|
||||
|
||||
subscribe(evt.BlockingCacheGroupChanged, func(listType lists.ListCacheType, groupName string, cnt int) {
|
||||
lastListGroupRefresh.Set(float64(time.Now().Unix()))
|
||||
|
||||
switch listType {
|
||||
case lists.ListCacheTypeBlacklist:
|
||||
blacklistCnt.WithLabelValues(groupName).Set(float64(cnt))
|
||||
case lists.ListCacheTypeWhitelist:
|
||||
whitelistCnt.WithLabelValues(groupName).Set(float64(cnt))
|
||||
case lists.ListCacheTypeDenylist:
|
||||
denylistCnt.WithLabelValues(groupName).Set(float64(cnt))
|
||||
case lists.ListCacheTypeAllowlist:
|
||||
allowlistCnt.WithLabelValues(groupName).Set(float64(cnt))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -82,26 +83,26 @@ func enabledGauge() prometheus.Gauge {
|
|||
return enabledGauge
|
||||
}
|
||||
|
||||
func blacklistGauge() *prometheus.GaugeVec {
|
||||
blacklistCnt := prometheus.NewGaugeVec(
|
||||
func denylistGauge() *prometheus.GaugeVec {
|
||||
denylistCnt := prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "blocky_blacklist_cache",
|
||||
Help: "Number of entries in the blacklist cache",
|
||||
Name: "blocky_denylist_cache",
|
||||
Help: "Number of entries in the denylist cache",
|
||||
}, []string{"group"},
|
||||
)
|
||||
|
||||
return blacklistCnt
|
||||
return denylistCnt
|
||||
}
|
||||
|
||||
func whitelistGauge() *prometheus.GaugeVec {
|
||||
whitelistCnt := prometheus.NewGaugeVec(
|
||||
func allowlistGauge() *prometheus.GaugeVec {
|
||||
allowlistCnt := prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "blocky_whitelist_cache",
|
||||
Help: "Number of entries in the whitelist cache",
|
||||
Name: "blocky_allowlist_cache",
|
||||
Help: "Number of entries in the allowlist cache",
|
||||
}, []string{"group"},
|
||||
)
|
||||
|
||||
return whitelistCnt
|
||||
return allowlistCnt
|
||||
}
|
||||
|
||||
func lastListGroupRefresh() prometheus.Gauge {
|
||||
|
|
|
@ -21,8 +21,8 @@ markdown_extensions:
|
|||
- abbr
|
||||
- pymdownx.snippets
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- pymdownx.highlight
|
||||
- pymdownx.superfences
|
||||
- admonition
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// ResponseType represents the type of the response ENUM(
|
||||
|
@ -67,6 +66,5 @@ type Request struct {
|
|||
Protocol RequestProtocol
|
||||
ClientNames []string
|
||||
Req *dns.Msg
|
||||
Log *logrus.Entry
|
||||
RequestTS time.Time
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ func (d *DatabaseWriter) periodicFlush(ctx context.Context) {
|
|||
case <-ticker.C:
|
||||
err := d.doDBWrite()
|
||||
|
||||
util.LogOnError("can't write entries to the database: ", err)
|
||||
util.LogOnError(ctx, "can't write entries to the database: ", err)
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
|
|
@ -85,7 +85,7 @@ func (d *FileWriter) CleanUp() {
|
|||
// search for log files, which names starts with date
|
||||
for _, f := range files {
|
||||
if strings.HasSuffix(f.Name(), ".log") && len(f.Name()) > 10 {
|
||||
t, err := time.Parse("2006-01-02", f.Name()[:10])
|
||||
t, err := time.ParseInLocation("2006-01-02", f.Name()[:10], time.Local)
|
||||
if err == nil {
|
||||
differenceDays := uint64(time.Since(t).Hours() / hoursPerDay)
|
||||
if d.logRetentionDays > 0 && differenceDays > d.logRetentionDays {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package querylog
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
|
@ -19,22 +20,36 @@ func NewLoggerWriter() *LoggerWriter {
|
|||
}
|
||||
|
||||
func (d *LoggerWriter) Write(entry *LogEntry) {
|
||||
d.logger.WithFields(
|
||||
logrus.Fields{
|
||||
"client_ip": entry.ClientIP,
|
||||
"client_names": strings.Join(entry.ClientNames, "; "),
|
||||
"response_reason": entry.ResponseReason,
|
||||
"response_type": entry.ResponseType,
|
||||
"response_code": entry.ResponseCode,
|
||||
"question_name": entry.QuestionName,
|
||||
"question_type": entry.QuestionType,
|
||||
"answer": entry.Answer,
|
||||
"duration_ms": entry.DurationMs,
|
||||
"hostname": util.HostnameString(),
|
||||
},
|
||||
).Infof("query resolved")
|
||||
fields := LogEntryFields(entry)
|
||||
|
||||
d.logger.WithFields(fields).Infof("query resolved")
|
||||
}
|
||||
|
||||
func (d *LoggerWriter) CleanUp() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
func LogEntryFields(entry *LogEntry) logrus.Fields {
|
||||
return withoutZeroes(logrus.Fields{
|
||||
"client_ip": entry.ClientIP,
|
||||
"client_names": strings.Join(entry.ClientNames, "; "),
|
||||
"response_reason": entry.ResponseReason,
|
||||
"response_type": entry.ResponseType,
|
||||
"response_code": entry.ResponseCode,
|
||||
"question_name": entry.QuestionName,
|
||||
"question_type": entry.QuestionType,
|
||||
"answer": entry.Answer,
|
||||
"duration_ms": entry.DurationMs,
|
||||
"hostname": util.HostnameString(),
|
||||
})
|
||||
}
|
||||
|
||||
func withoutZeroes(fields logrus.Fields) logrus.Fields {
|
||||
for k, v := range fields {
|
||||
if reflect.ValueOf(v).IsZero() {
|
||||
delete(fields, k)
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package querylog
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -34,4 +35,50 @@ var _ = Describe("LoggerWriter", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("LogEntryFields", func() {
|
||||
It("should return log fields", func() {
|
||||
entry := LogEntry{
|
||||
ClientIP: "ip",
|
||||
DurationMs: 100,
|
||||
QuestionType: "qtype",
|
||||
ResponseCode: "rcode",
|
||||
}
|
||||
|
||||
fields := LogEntryFields(&entry)
|
||||
|
||||
Expect(fields).Should(HaveKeyWithValue("client_ip", entry.ClientIP))
|
||||
Expect(fields).Should(HaveKeyWithValue("duration_ms", entry.DurationMs))
|
||||
Expect(fields).Should(HaveKeyWithValue("question_type", entry.QuestionType))
|
||||
Expect(fields).Should(HaveKeyWithValue("response_code", entry.ResponseCode))
|
||||
Expect(fields).Should(HaveKey("hostname"))
|
||||
|
||||
Expect(fields).ShouldNot(HaveKey("client_names"))
|
||||
Expect(fields).ShouldNot(HaveKey("question_name"))
|
||||
})
|
||||
})
|
||||
|
||||
DescribeTable("withoutZeroes",
|
||||
func(value any, isZero bool) {
|
||||
fields := withoutZeroes(logrus.Fields{"a": value})
|
||||
|
||||
if isZero {
|
||||
Expect(fields).Should(BeEmpty())
|
||||
} else {
|
||||
Expect(fields).ShouldNot(BeEmpty())
|
||||
}
|
||||
},
|
||||
Entry("empty string",
|
||||
"",
|
||||
true),
|
||||
Entry("non-empty string",
|
||||
"something",
|
||||
false),
|
||||
Entry("zero int",
|
||||
0,
|
||||
true),
|
||||
Entry("non-zero int",
|
||||
1,
|
||||
false),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -79,16 +79,16 @@ type status struct {
|
|||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// BlockingResolver checks request's question (domain name) against black and white lists
|
||||
// BlockingResolver checks request's question (domain name) against allow/denylists
|
||||
type BlockingResolver struct {
|
||||
configurable[*config.Blocking]
|
||||
NextResolver
|
||||
typed
|
||||
|
||||
blacklistMatcher *lists.ListCache
|
||||
whitelistMatcher *lists.ListCache
|
||||
denylistMatcher *lists.ListCache
|
||||
allowlistMatcher *lists.ListCache
|
||||
blockHandler blockHandler
|
||||
whitelistOnlyGroups map[string]bool
|
||||
allowlistOnlyGroups map[string]bool
|
||||
status *status
|
||||
clientGroupsBlock map[string][]string
|
||||
redisClient *redis.Client
|
||||
|
@ -125,11 +125,11 @@ func NewBlockingResolver(ctx context.Context,
|
|||
|
||||
downloader := lists.NewDownloader(cfg.Loading.Downloads, bootstrap.NewHTTPTransport())
|
||||
|
||||
blacklistMatcher, blErr := lists.NewListCache(ctx, lists.ListCacheTypeBlacklist,
|
||||
cfg.Loading, cfg.BlackLists, downloader)
|
||||
whitelistMatcher, wlErr := lists.NewListCache(ctx, lists.ListCacheTypeWhitelist,
|
||||
cfg.Loading, cfg.WhiteLists, downloader)
|
||||
whitelistOnlyGroups := determineWhitelistOnlyGroups(&cfg)
|
||||
denylistMatcher, blErr := lists.NewListCache(ctx, lists.ListCacheTypeDenylist,
|
||||
cfg.Loading, cfg.Denylists, downloader)
|
||||
allowlistMatcher, wlErr := lists.NewListCache(ctx, lists.ListCacheTypeAllowlist,
|
||||
cfg.Loading, cfg.Allowlists, downloader)
|
||||
allowlistOnlyGroups := determineAllowlistOnlyGroups(&cfg)
|
||||
|
||||
err = multierror.Append(err, blErr, wlErr).ErrorOrNil()
|
||||
if err != nil {
|
||||
|
@ -141,9 +141,9 @@ func NewBlockingResolver(ctx context.Context,
|
|||
typed: withType("blocking"),
|
||||
|
||||
blockHandler: blockHandler,
|
||||
blacklistMatcher: blacklistMatcher,
|
||||
whitelistMatcher: whitelistMatcher,
|
||||
whitelistOnlyGroups: whitelistOnlyGroups,
|
||||
denylistMatcher: denylistMatcher,
|
||||
allowlistMatcher: allowlistMatcher,
|
||||
allowlistOnlyGroups: allowlistOnlyGroups,
|
||||
status: &status{
|
||||
enabled: true,
|
||||
enableTimer: time.NewTimer(0),
|
||||
|
@ -165,7 +165,6 @@ func NewBlockingResolver(ctx context.Context,
|
|||
err = evt.Bus().SubscribeOnce(evt.ApplicationStarted, func(_ ...string) {
|
||||
go res.initFQDNIPCache(ctx)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -174,18 +173,20 @@ func NewBlockingResolver(ctx context.Context,
|
|||
}
|
||||
|
||||
func (r *BlockingResolver) redisSubscriber(ctx context.Context) {
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
for {
|
||||
select {
|
||||
case em := <-r.redisClient.EnabledChannel:
|
||||
if em != nil {
|
||||
r.log().Debug("Received state from redis: ", em)
|
||||
logger.Debug("Received state from redis: ", em)
|
||||
|
||||
if em.State {
|
||||
r.internalEnableBlocking()
|
||||
} else {
|
||||
err := r.internalDisableBlocking(ctx, em.Duration, em.Groups)
|
||||
if err != nil {
|
||||
r.log().Warn("Blocking couldn't be disabled:", err)
|
||||
logger.Warn("Blocking couldn't be disabled:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,18 +197,18 @@ func (r *BlockingResolver) redisSubscriber(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
// RefreshLists triggers the refresh of all black and white lists in the cache
|
||||
// RefreshLists triggers the refresh of all allow/denylists in the cache
|
||||
func (r *BlockingResolver) RefreshLists() error {
|
||||
var err *multierror.Error
|
||||
|
||||
err = multierror.Append(err, r.blacklistMatcher.Refresh())
|
||||
err = multierror.Append(err, r.whitelistMatcher.Refresh())
|
||||
err = multierror.Append(err, r.denylistMatcher.Refresh())
|
||||
err = multierror.Append(err, r.allowlistMatcher.Refresh())
|
||||
|
||||
return err.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (r *BlockingResolver) retrieveAllBlockingGroups() []string {
|
||||
result := maps.Keys(r.cfg.BlackLists)
|
||||
result := maps.Keys(r.cfg.Denylists)
|
||||
|
||||
result = append(result, "default")
|
||||
slices.Sort(result)
|
||||
|
@ -215,7 +216,7 @@ func (r *BlockingResolver) retrieveAllBlockingGroups() []string {
|
|||
return result
|
||||
}
|
||||
|
||||
// EnableBlocking enables the blocking against the blacklists
|
||||
// EnableBlocking enables the blocking against the denylists
|
||||
func (r *BlockingResolver) EnableBlocking(ctx context.Context) {
|
||||
r.internalEnableBlocking()
|
||||
|
||||
|
@ -268,6 +269,7 @@ func (r *BlockingResolver) internalDisableBlocking(ctx context.Context, duration
|
|||
return fmt.Errorf("group '%s' is unknown", g)
|
||||
}
|
||||
}
|
||||
|
||||
s.disabledGroups = disableGroups
|
||||
}
|
||||
|
||||
|
@ -281,6 +283,7 @@ func (r *BlockingResolver) internalDisableBlocking(ctx context.Context, duration
|
|||
} else {
|
||||
log.Log().Infof("disable blocking for %s for group(s) '%s'", duration,
|
||||
log.EscapeInput(strings.Join(s.disabledGroups, "; ")))
|
||||
|
||||
s.enableTimer = time.AfterFunc(duration, func() {
|
||||
r.EnableBlocking(ctx)
|
||||
log.Log().Info("blocking enabled again")
|
||||
|
@ -308,13 +311,13 @@ func (r *BlockingResolver) BlockingStatus() api.BlockingStatus {
|
|||
}
|
||||
}
|
||||
|
||||
// returns groups, which have only whitelist entries
|
||||
func determineWhitelistOnlyGroups(cfg *config.Blocking) (result map[string]bool) {
|
||||
result = make(map[string]bool, len(cfg.WhiteLists))
|
||||
// returns groups, which have only allowlist entries
|
||||
func determineAllowlistOnlyGroups(cfg *config.Blocking) (result map[string]bool) {
|
||||
result = make(map[string]bool, len(cfg.Allowlists))
|
||||
|
||||
for g, links := range cfg.WhiteLists {
|
||||
for g, links := range cfg.Allowlists {
|
||||
if len(links) > 0 {
|
||||
if _, found := cfg.BlackLists[g]; !found {
|
||||
if _, found := cfg.Denylists[g]; !found {
|
||||
result[g] = true
|
||||
}
|
||||
}
|
||||
|
@ -341,16 +344,16 @@ func (r *BlockingResolver) handleBlocked(logger *logrus.Entry,
|
|||
func (r *BlockingResolver) LogConfig(logger *logrus.Entry) {
|
||||
r.cfg.LogConfig(logger)
|
||||
|
||||
logger.Info("blacklist cache entries:")
|
||||
log.WithIndent(logger, " ", r.blacklistMatcher.LogConfig)
|
||||
logger.Info("denylist cache entries:")
|
||||
log.WithIndent(logger, " ", r.denylistMatcher.LogConfig)
|
||||
|
||||
logger.Info("whitelist cache entries:")
|
||||
log.WithIndent(logger, " ", r.whitelistMatcher.LogConfig)
|
||||
logger.Info("allowlist cache entries:")
|
||||
log.WithIndent(logger, " ", r.allowlistMatcher.LogConfig)
|
||||
}
|
||||
|
||||
func (r *BlockingResolver) hasWhiteListOnlyAllowed(groupsToCheck []string) bool {
|
||||
func (r *BlockingResolver) hasAllowlistOnlyAllowed(groupsToCheck []string) bool {
|
||||
for _, group := range groupsToCheck {
|
||||
if _, found := r.whitelistOnlyGroups[group]; found {
|
||||
if _, found := r.allowlistOnlyGroups[group]; found {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -358,31 +361,31 @@ func (r *BlockingResolver) hasWhiteListOnlyAllowed(groupsToCheck []string) bool
|
|||
return false
|
||||
}
|
||||
|
||||
func (r *BlockingResolver) handleBlacklist(ctx context.Context, groupsToCheck []string,
|
||||
func (r *BlockingResolver) handleDenylist(ctx context.Context, groupsToCheck []string,
|
||||
request *model.Request, logger *logrus.Entry,
|
||||
) (bool, *model.Response, error) {
|
||||
logger.WithField("groupsToCheck", strings.Join(groupsToCheck, "; ")).Debug("checking groups for request")
|
||||
whitelistOnlyAllowed := r.hasWhiteListOnlyAllowed(groupsToCheck)
|
||||
allowlistOnlyAllowed := r.hasAllowlistOnlyAllowed(groupsToCheck)
|
||||
|
||||
for _, question := range request.Req.Question {
|
||||
domain := util.ExtractDomain(question)
|
||||
logger := logger.WithField("domain", domain)
|
||||
|
||||
if groups := r.matches(groupsToCheck, r.whitelistMatcher, domain); len(groups) > 0 {
|
||||
logger.WithField("groups", groups).Debugf("domain is whitelisted")
|
||||
if groups := r.matches(groupsToCheck, r.allowlistMatcher, domain); len(groups) > 0 {
|
||||
logger.WithField("groups", groups).Debugf("domain is allowlisted")
|
||||
|
||||
resp, err := r.next.Resolve(ctx, request)
|
||||
|
||||
return true, resp, err
|
||||
}
|
||||
|
||||
if whitelistOnlyAllowed {
|
||||
resp, err := r.handleBlocked(logger, request, question, "BLOCKED (WHITELIST ONLY)")
|
||||
if allowlistOnlyAllowed {
|
||||
resp, err := r.handleBlocked(logger, request, question, "BLOCKED (ALLOWLIST ONLY)")
|
||||
|
||||
return true, resp, err
|
||||
}
|
||||
|
||||
if groups := r.matches(groupsToCheck, r.blacklistMatcher, domain); len(groups) > 0 {
|
||||
if groups := r.matches(groupsToCheck, r.denylistMatcher, domain); len(groups) > 0 {
|
||||
resp, err := r.handleBlocked(logger, request, question, fmt.Sprintf("BLOCKED (%s)", strings.Join(groups, ",")))
|
||||
|
||||
return true, resp, err
|
||||
|
@ -392,13 +395,13 @@ func (r *BlockingResolver) handleBlacklist(ctx context.Context, groupsToCheck []
|
|||
return false, nil, nil
|
||||
}
|
||||
|
||||
// Resolve checks the query against the blacklist and delegates to next resolver if domain is not blocked
|
||||
// Resolve checks the query against the denylist and delegates to next resolver if domain is not blocked
|
||||
func (r *BlockingResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, "blacklist_resolver")
|
||||
ctx, logger := r.log(ctx)
|
||||
groupsToCheck := r.groupsToCheckForClient(request)
|
||||
|
||||
if len(groupsToCheck) > 0 {
|
||||
handled, resp, err := r.handleBlacklist(ctx, groupsToCheck, request, logger)
|
||||
handled, resp, err := r.handleDenylist(ctx, groupsToCheck, request, logger)
|
||||
if handled {
|
||||
return resp, err
|
||||
}
|
||||
|
@ -412,9 +415,9 @@ func (r *BlockingResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
if len(entryToCheck) > 0 {
|
||||
logger := logger.WithField("response_entry", entryToCheck)
|
||||
|
||||
if groups := r.matches(groupsToCheck, r.whitelistMatcher, entryToCheck); len(groups) > 0 {
|
||||
logger.WithField("groups", groups).Debugf("%s is whitelisted", tName)
|
||||
} else if groups := r.matches(groupsToCheck, r.blacklistMatcher, entryToCheck); len(groups) > 0 {
|
||||
if groups := r.matches(groupsToCheck, r.allowlistMatcher, entryToCheck); len(groups) > 0 {
|
||||
logger.WithField("groups", groups).Debugf("%s is allowlisted", tName)
|
||||
} else if groups := r.matches(groupsToCheck, r.denylistMatcher, entryToCheck); len(groups) > 0 {
|
||||
return r.handleBlocked(logger, request, request.Req.Question[0], fmt.Sprintf("BLOCKED %s (%s)", tName,
|
||||
strings.Join(groups, ",")))
|
||||
}
|
||||
|
@ -575,7 +578,9 @@ func (b ipBlockHandler) handleBlock(question dns.Question, response *dns.Msg) {
|
|||
}
|
||||
|
||||
func (r *BlockingResolver) queryForFQIdentifierIPs(ctx context.Context, identifier string) (*[]net.IP, time.Duration) {
|
||||
prefixedLog := log.WithPrefix(r.log(), "client_id_cache")
|
||||
ctx, logger := r.logWith(ctx, func(logger *logrus.Entry) *logrus.Entry {
|
||||
return log.WithPrefix(logger, "client_id_cache")
|
||||
})
|
||||
|
||||
var result []net.IP
|
||||
|
||||
|
@ -584,7 +589,6 @@ func (r *BlockingResolver) queryForFQIdentifierIPs(ctx context.Context, identifi
|
|||
for _, qType := range []uint16{dns.TypeA, dns.TypeAAAA} {
|
||||
resp, err := r.next.Resolve(ctx, &model.Request{
|
||||
Req: util.NewMsgWithQuestion(identifier, dns.Type(qType)),
|
||||
Log: prefixedLog,
|
||||
})
|
||||
|
||||
if err == nil && resp.Res.Rcode == dns.RcodeSuccess {
|
||||
|
@ -598,11 +602,16 @@ func (r *BlockingResolver) queryForFQIdentifierIPs(ctx context.Context, identifi
|
|||
result = append(result, v.AAAA)
|
||||
}
|
||||
}
|
||||
|
||||
prefixedLog.Debugf("resolved IPs '%v' for fq identifier '%s'", result, identifier)
|
||||
}
|
||||
}
|
||||
|
||||
if len(result) != 0 {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"ips": result,
|
||||
"client_id": identifier,
|
||||
}).Debug("resolved client IPs")
|
||||
}
|
||||
|
||||
return &result, ttl
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"gr1": config.NewBytesSources(group1File.Path),
|
||||
"gr2": config.NewBytesSources(group2File.Path),
|
||||
},
|
||||
|
@ -125,7 +125,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"gr1": config.NewBytesSources(group1File.Path),
|
||||
"gr2": config.NewBytesSources(group2File.Path),
|
||||
},
|
||||
|
@ -164,7 +164,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
sutConfig = config.Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"gr1": {config.TextBytesSource("/regex/")},
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
|
@ -176,7 +176,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
}
|
||||
})
|
||||
|
||||
When("Domain is on the black list", func() {
|
||||
When("Domain is on the denylist", func() {
|
||||
It("should block request", func() {
|
||||
Eventually(sut.Resolve).
|
||||
WithContext(ctx).
|
||||
|
@ -196,7 +196,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockTTL: config.Duration(6 * time.Hour),
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"gr1": config.NewBytesSources(group1File.Path),
|
||||
"gr2": config.NewBytesSources(group2File.Path),
|
||||
"defaultGroup": config.NewBytesSources(defaultGroupFile.Path),
|
||||
|
@ -216,7 +216,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
|
||||
When("client name is defined in client groups block", func() {
|
||||
It("should block the A query if domain is on the black list (single)", func() {
|
||||
It("should block the A query if domain is on the denylist (single)", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "1.2.1.2", "client1"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -227,7 +227,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
})
|
||||
It("should block the A query if domain is on the black list (multipart 1)", func() {
|
||||
It("should block the A query if domain is on the denylist (multipart 1)", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "1.2.1.2", "client2"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -238,7 +238,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
})
|
||||
It("should block the A query if domain is on the black list (multipart 2)", func() {
|
||||
It("should block the A query if domain is on the denylist (multipart 2)", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "1.2.1.2", "client3"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -249,7 +249,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
})
|
||||
It("should block the A query if domain is on the black list (merged)", func() {
|
||||
It("should block the A query if domain is on the denylist (merged)", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("blocked2.com.", A, "1.2.1.2", "client3"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -260,7 +260,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
})
|
||||
It("should block the AAAA query if domain is on the black list", func() {
|
||||
It("should block the AAAA query if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", AAAA, "1.2.1.2", "client1"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -271,18 +271,18 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
})
|
||||
It("should block the HTTPS query if domain is on the black list", func() {
|
||||
It("should block the HTTPS query if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", HTTPS, "1.2.1.2", "client1"))).
|
||||
Should(HaveReturnCode(dns.RcodeNameError))
|
||||
})
|
||||
It("should block the MX query if domain is on the black list", func() {
|
||||
It("should block the MX query if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", MX, "1.2.1.2", "client1"))).
|
||||
Should(HaveReturnCode(dns.RcodeNameError))
|
||||
})
|
||||
})
|
||||
|
||||
When("Client ip is defined in client groups block", func() {
|
||||
It("should block the query if domain is on the black list", func() {
|
||||
It("should block the query if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "192.168.178.55", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -295,7 +295,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
})
|
||||
When("Client CIDR (10.43.8.64 - 10.43.8.79) is defined in client groups block", func() {
|
||||
It("should not block the query for 10.43.8.63 if domain is on the black list", func() {
|
||||
It("should not block the query for 10.43.8.63 if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "10.43.8.63", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -307,7 +307,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
// was delegated to next resolver
|
||||
m.AssertExpectations(GinkgoT())
|
||||
})
|
||||
It("should not block the query for 10.43.8.80 if domain is on the black list", func() {
|
||||
It("should not block the query for 10.43.8.80 if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "10.43.8.80", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -322,7 +322,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
|
||||
When("Client CIDR (10.43.8.64 - 10.43.8.79) is defined in client groups block", func() {
|
||||
It("should block the query for 10.43.8.64 if domain is on the black list", func() {
|
||||
It("should block the query for 10.43.8.64 if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "10.43.8.64", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -333,7 +333,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
})
|
||||
It("should block the query for 10.43.8.79 if domain is on the black list", func() {
|
||||
It("should block the query for 10.43.8.79 if domain is on the denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "10.43.8.79", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -402,7 +402,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"defaultGroup": config.NewBytesSources(defaultGroupFile.Path),
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
|
@ -428,7 +428,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"defaultGroup": config.NewBytesSources(defaultGroupFile.Path),
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
|
@ -473,7 +473,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockTTL: config.Duration(6 * time.Hour),
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"defaultGroup": config.NewBytesSources(defaultGroupFile.Path),
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
|
@ -511,7 +511,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
When("BlockType is custom IP only for ipv4", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"defaultGroup": config.NewBytesSources(defaultGroupFile.Path),
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
|
@ -535,13 +535,13 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
When("Blacklist contains IP", func() {
|
||||
When("Denylist contains IP", func() {
|
||||
When("IP4", func() {
|
||||
BeforeEach(func() {
|
||||
// return defined IP as response
|
||||
mockAnswer, _ = util.NewMsgWithAnswer("example.com.", 300, A, "123.145.123.145")
|
||||
})
|
||||
It("should block query, if lookup result contains blacklisted IP", func() {
|
||||
It("should block query, if lookup result contains denylisted IP", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("example.com.", A, "1.2.1.2", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -561,7 +561,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
AAAA, "2001:0db8:85a3:08d3::0370:7344",
|
||||
)
|
||||
})
|
||||
It("should block query, if lookup result contains blacklisted IP", func() {
|
||||
It("should block query, if lookup result contains denylisted IP", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("example.com.", AAAA, "1.2.1.2", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -575,7 +575,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
When("blacklist contains domain which is CNAME in response", func() {
|
||||
When("denylist contains domain which is CNAME in response", func() {
|
||||
BeforeEach(func() {
|
||||
// reconfigure mock, to return CNAMEs
|
||||
rr1, _ := dns.NewRR("example.com 300 IN CNAME domain.com")
|
||||
|
@ -584,7 +584,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
mockAnswer = new(dns.Msg)
|
||||
mockAnswer.Answer = []dns.RR{rr1, rr2, rr3}
|
||||
})
|
||||
It("should block the query, if response contains a CNAME with domain on a blacklist", func() {
|
||||
It("should block the query, if response contains a CNAME with domain on a denylist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("example.com.", A, "1.2.1.2", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -598,14 +598,14 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("Whitelisting", func() {
|
||||
When("Requested domain is on black and white list", func() {
|
||||
Describe("Allowlisting", func() {
|
||||
When("Requested domain is on black and allowlist", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
WhiteLists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
Denylists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
Allowlists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
},
|
||||
|
@ -625,12 +625,12 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
When("Only whitelist is defined", func() {
|
||||
When("Only allowlist is defined", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockType: "zeroIP",
|
||||
BlockTTL: config.Duration(60 * time.Second),
|
||||
WhiteLists: map[string][]config.BytesSource{
|
||||
Allowlists: map[string][]config.BytesSource{
|
||||
"gr1": config.NewBytesSources(group1File.Path),
|
||||
"gr2": config.NewBytesSources(group2File.Path),
|
||||
},
|
||||
|
@ -642,8 +642,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
},
|
||||
}
|
||||
})
|
||||
It("should block everything else except domains on the white list with default group", func() {
|
||||
By("querying domain on the whitelist", func() {
|
||||
It("should block everything else except domains on the allowlist with default group", func() {
|
||||
By("querying domain on the allowlist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "1.2.1.2", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -656,7 +656,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
m.AssertExpectations(GinkgoT())
|
||||
})
|
||||
|
||||
By("querying another domain, which is not on the whitelist", func() {
|
||||
By("querying another domain, which is not on the allowlist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("google.com.", A, "1.2.1.2", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -664,15 +664,15 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveTTL(BeNumerically("==", 60)),
|
||||
HaveResponseType(ResponseTypeBLOCKED),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
HaveReason("BLOCKED (WHITELIST ONLY)"),
|
||||
HaveReason("BLOCKED (ALLOWLIST ONLY)"),
|
||||
))
|
||||
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
})
|
||||
It("should block everything else except domains on the white list "+
|
||||
"if multiple white list only groups are defined", func() {
|
||||
By("querying domain on the whitelist", func() {
|
||||
It("should block everything else except domains on the allowlist "+
|
||||
"if multiple allowlist only groups are defined", func() {
|
||||
By("querying domain on the allowlist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "1.2.1.2", "one-client"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -685,7 +685,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
m.AssertExpectations(GinkgoT())
|
||||
})
|
||||
|
||||
By("querying another domain, which is not on the whitelist", func() {
|
||||
By("querying another domain, which is not on the allowlist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("blocked2.com.", A, "1.2.1.2", "one-client"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -693,14 +693,14 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
HaveTTL(BeNumerically("==", 60)),
|
||||
HaveResponseType(ResponseTypeBLOCKED),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
HaveReason("BLOCKED (WHITELIST ONLY)"),
|
||||
HaveReason("BLOCKED (ALLOWLIST ONLY)"),
|
||||
))
|
||||
Expect(m.Calls).Should(HaveLen(1))
|
||||
})
|
||||
})
|
||||
It("should block everything else except domains on the white list "+
|
||||
"if multiple white list only groups are defined", func() {
|
||||
By("querying domain on the whitelist group 1", func() {
|
||||
It("should block everything else except domains on the allowlist "+
|
||||
"if multiple allowlist only groups are defined", func() {
|
||||
By("querying domain on the allowlist group 1", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("domain1.com.", A, "1.2.1.2", "all-client"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -713,7 +713,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
m.AssertExpectations(GinkgoT())
|
||||
})
|
||||
|
||||
By("querying another domain, which is in the whitelist group 1", func() {
|
||||
By("querying another domain, which is in the allowlist group 1", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("blocked2.com.", A, "1.2.1.2", "all-client"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -726,20 +726,20 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
When("IP address is on black and white list", func() {
|
||||
When("IP address is on black and allowlist", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
WhiteLists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(defaultGroupFile.Path)},
|
||||
Denylists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
Allowlists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(defaultGroupFile.Path)},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
},
|
||||
}
|
||||
mockAnswer, _ = util.NewMsgWithAnswer("example.com.", 300, A, "123.145.123.145")
|
||||
})
|
||||
It("should not block if DNS answer contains IP from the white list", func() {
|
||||
It("should not block if DNS answer contains IP from the allowlist", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("example.com.", A, "1.2.1.2", "unknown"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -756,9 +756,9 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
Describe("Delegate request to next resolver", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
BlackLists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
BlockType: "ZEROIP",
|
||||
BlockTTL: config.Duration(time.Minute),
|
||||
Denylists: map[string][]config.BytesSource{"gr1": config.NewBytesSources(group1File.Path)},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"gr1"},
|
||||
},
|
||||
|
@ -768,7 +768,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
// was delegated to next resolver
|
||||
m.AssertExpectations(GinkgoT())
|
||||
})
|
||||
When("domain is not on the black list", func() {
|
||||
When("domain is not on the denylist", func() {
|
||||
It("should delegate to next resolver", func() {
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("example.com.", A, "1.2.1.2", "unknown"))).
|
||||
Should(
|
||||
|
@ -801,7 +801,7 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
Describe("Control status via API", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.Blocking{
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"defaultGroup": config.NewBytesSources(defaultGroupFile.Path),
|
||||
"group1": config.NewBytesSources(group1File.Path),
|
||||
},
|
||||
|
@ -1124,8 +1124,8 @@ var _ = Describe("BlockingResolver", Label("blockingResolver"), func() {
|
|||
When("strategy is failOnError", func() {
|
||||
It("should fail if lists can't be downloaded", func() {
|
||||
_, err := NewBlockingResolver(ctx, config.Blocking{
|
||||
BlackLists: map[string][]config.BytesSource{"gr1": config.NewBytesSources("wrongPath")},
|
||||
WhiteLists: map[string][]config.BytesSource{"whitelist": config.NewBytesSources("wrongPath")},
|
||||
Denylists: map[string][]config.BytesSource{"gr1": config.NewBytesSources("wrongPath")},
|
||||
Allowlists: map[string][]config.BytesSource{"allowlist": config.NewBytesSources("wrongPath")},
|
||||
Loading: config.SourceLoading{
|
||||
Init: config.Init{Strategy: config.InitStrategyFailOnError},
|
||||
},
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
@ -70,13 +69,15 @@ func NewBootstrap(ctx context.Context, cfg *config.Config) (b *Bootstrap, err er
|
|||
dialer: new(net.Dialer),
|
||||
}
|
||||
|
||||
ctx, logger := b.log(ctx)
|
||||
|
||||
bootstraped, err := newBootstrapedResolvers(b, cfg.BootstrapDNS, cfg.Upstreams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(bootstraped) == 0 {
|
||||
b.log().Info("bootstrapDns is not configured, will use system resolver")
|
||||
logger.Info("bootstrapDns is not configured, will use system resolver")
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
@ -116,10 +117,9 @@ func (b *Bootstrap) Resolve(ctx context.Context, request *model.Request) (*model
|
|||
}
|
||||
|
||||
// Add bootstrap prefix to all inner resolver logs
|
||||
req := *request
|
||||
req.Log = log.WithPrefix(req.Log, b.Type())
|
||||
ctx, _ = b.log(ctx)
|
||||
|
||||
return b.resolver.Resolve(ctx, &req)
|
||||
return b.resolver.Resolve(ctx, request)
|
||||
}
|
||||
|
||||
func (b *Bootstrap) UpstreamIPs(ctx context.Context, r *UpstreamResolver) (*IPSet, error) {
|
||||
|
@ -156,19 +156,18 @@ func (b *Bootstrap) resolveUpstream(ctx context.Context, r Resolver, host string
|
|||
|
||||
// NewHTTPTransport returns a new http.Transport that uses b to resolve hostnames
|
||||
func (b *Bootstrap) NewHTTPTransport() *http.Transport {
|
||||
if b.resolver == nil {
|
||||
return &http.Transport{
|
||||
DialContext: b.dialer.DialContext,
|
||||
}
|
||||
}
|
||||
transport := util.DefaultHTTPTransport()
|
||||
transport.DialContext = b.dialContext
|
||||
|
||||
return &http.Transport{
|
||||
DialContext: b.dialContext,
|
||||
}
|
||||
return transport
|
||||
}
|
||||
|
||||
func (b *Bootstrap) dialContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
logger := b.log().WithFields(logrus.Fields{"network": network, "addr": addr})
|
||||
if b.resolver == nil {
|
||||
return b.dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
|
||||
ctx, logger := b.logWithFields(ctx, logrus.Fields{"network": network, "addr": addr})
|
||||
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
|
@ -234,9 +233,10 @@ func (b *Bootstrap) resolveType(ctx context.Context, hostname string, qType dns.
|
|||
return []net.IP{ip}, nil
|
||||
}
|
||||
|
||||
ctx, _ = b.log(ctx)
|
||||
|
||||
req := model.Request{
|
||||
Req: util.NewMsgWithQuestion(hostname, qType),
|
||||
Log: b.log(),
|
||||
}
|
||||
|
||||
rsp, err := b.resolver.Resolve(ctx, &req)
|
||||
|
|
|
@ -7,13 +7,12 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
. "github.com/0xERR0R/blocky/helpertest"
|
||||
|
@ -79,10 +78,15 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
|
|||
})
|
||||
|
||||
Describe("HTTP transport", func() {
|
||||
It("should use the system resolver", func() {
|
||||
It("should use Go default values", func() {
|
||||
transport := sut.NewHTTPTransport()
|
||||
|
||||
Expect(transport).ShouldNot(BeNil())
|
||||
|
||||
Expect(
|
||||
reflect.ValueOf(transport.Proxy).Pointer(),
|
||||
).Should(Equal(
|
||||
reflect.ValueOf(http.ProxyFromEnvironment).Pointer(),
|
||||
))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -310,7 +314,6 @@ var _ = Describe("Bootstrap", Label("bootstrap"), func() {
|
|||
It("uses the bootstrap upstream", func() {
|
||||
mainReq := &model.Request{
|
||||
Req: util.NewMsgWithQuestion("example.com.", A),
|
||||
Log: logrus.NewEntry(log.Log()),
|
||||
}
|
||||
|
||||
mockUpstreamServer := NewMockUDPUpstreamServer().WithAnswerRR("example.com 123 IN A 123.124.122.122")
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/0xERR0R/blocky/cache/expirationcache"
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/evt"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/redis"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
|
@ -107,11 +106,11 @@ func configureCaches(ctx context.Context, c *CachingResolver, cfg *config.Cachin
|
|||
|
||||
func (r *CachingResolver) reloadCacheEntry(ctx context.Context, cacheKey string) (*[]byte, time.Duration) {
|
||||
qType, domainName := util.ExtractCacheKey(cacheKey)
|
||||
logger := r.log()
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
logger.Debugf("prefetching '%s' (%s)", util.Obfuscate(domainName), qType)
|
||||
|
||||
req := newRequest(dns.Fqdn(domainName), qType, logger)
|
||||
req := newRequest(dns.Fqdn(domainName), qType)
|
||||
response, err := r.next.Resolve(ctx, req)
|
||||
|
||||
if err == nil {
|
||||
|
@ -126,20 +125,22 @@ func (r *CachingResolver) reloadCacheEntry(ctx context.Context, cacheKey string)
|
|||
return &packed, r.adjustTTLs(response.Res.Answer)
|
||||
}
|
||||
} else {
|
||||
util.LogOnError(fmt.Sprintf("can't prefetch '%s' ", domainName), err)
|
||||
util.LogOnError(ctx, fmt.Sprintf("can't prefetch '%s' ", domainName), err)
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (r *CachingResolver) redisSubscriber(ctx context.Context) {
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
for {
|
||||
select {
|
||||
case rc := <-r.redisClient.CacheChannel:
|
||||
if rc != nil {
|
||||
r.log().Debug("Received key from redis: ", rc.Key)
|
||||
logger.Debug("Received key from redis: ", rc.Key)
|
||||
ttl := r.adjustTTLs(rc.Response.Res.Answer)
|
||||
r.putInCache(rc.Key, rc.Response, ttl, false)
|
||||
r.putInCache(ctx, rc.Key, rc.Response, ttl, false)
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
|
@ -158,7 +159,7 @@ func (r *CachingResolver) LogConfig(logger *logrus.Entry) {
|
|||
// Resolve checks if the current query should use the cache and if the result is already in
|
||||
// the cache and returns it or delegates to the next resolver
|
||||
func (r *CachingResolver) Resolve(ctx context.Context, request *model.Request) (response *model.Response, err error) {
|
||||
logger := log.WithPrefix(request.Log, "caching_resolver")
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
if !r.IsEnabled() || !isRequestCacheable(request) {
|
||||
logger.Debug("skip cache")
|
||||
|
@ -171,7 +172,7 @@ func (r *CachingResolver) Resolve(ctx context.Context, request *model.Request) (
|
|||
cacheKey := util.GenerateCacheKey(dns.Type(question.Qtype), domain)
|
||||
logger := logger.WithField("domain", util.Obfuscate(domain))
|
||||
|
||||
val, ttl := r.getFromCache(cacheKey)
|
||||
val, ttl := r.getFromCache(logger, cacheKey)
|
||||
|
||||
if val != nil {
|
||||
logger.Debug("domain is cached")
|
||||
|
@ -193,14 +194,14 @@ func (r *CachingResolver) Resolve(ctx context.Context, request *model.Request) (
|
|||
|
||||
if err == nil {
|
||||
cacheTTL := r.adjustTTLs(response.Res.Answer)
|
||||
r.putInCache(cacheKey, response, cacheTTL, true)
|
||||
r.putInCache(ctx, cacheKey, response, cacheTTL, true)
|
||||
}
|
||||
}
|
||||
|
||||
return response, err
|
||||
}
|
||||
|
||||
func (r *CachingResolver) getFromCache(key string) (*dns.Msg, time.Duration) {
|
||||
func (r *CachingResolver) getFromCache(logger *logrus.Entry, key string) (*dns.Msg, time.Duration) {
|
||||
val, ttl := r.resultCache.Get(key)
|
||||
if val == nil {
|
||||
return nil, 0
|
||||
|
@ -210,7 +211,7 @@ func (r *CachingResolver) getFromCache(key string) (*dns.Msg, time.Duration) {
|
|||
|
||||
err := res.Unpack(*val)
|
||||
if err != nil {
|
||||
r.log().Error("can't unpack cached entry. Cache malformed?", err)
|
||||
logger.Error("can't unpack cached entry. Cache malformed?", err)
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
@ -249,8 +250,8 @@ func isResponseCacheable(msg *dns.Msg) bool {
|
|||
return !msg.Truncated && !msg.CheckingDisabled
|
||||
}
|
||||
|
||||
func (r *CachingResolver) putInCache(cacheKey string, response *model.Response, ttl time.Duration,
|
||||
publish bool,
|
||||
func (r *CachingResolver) putInCache(
|
||||
ctx context.Context, cacheKey string, response *model.Response, ttl time.Duration, publish bool,
|
||||
) {
|
||||
respCopy := response.Res.Copy()
|
||||
|
||||
|
@ -258,7 +259,7 @@ func (r *CachingResolver) putInCache(cacheKey string, response *model.Response,
|
|||
util.RemoveEdns0Record(respCopy)
|
||||
|
||||
packed, err := respCopy.Pack()
|
||||
util.LogOnError("error on packing", err)
|
||||
util.LogOnError(ctx, "error on packing", err)
|
||||
|
||||
if err == nil {
|
||||
if response.Res.Rcode == dns.RcodeSuccess && isResponseCacheable(response.Res) {
|
||||
|
@ -317,7 +318,9 @@ func (r *CachingResolver) publishMetricsIfEnabled(event string, val interface{})
|
|||
}
|
||||
}
|
||||
|
||||
func (r *CachingResolver) FlushCaches(context.Context) {
|
||||
r.log().Debug("flush caches")
|
||||
func (r *CachingResolver) FlushCaches(ctx context.Context) {
|
||||
_, logger := r.log(ctx)
|
||||
|
||||
logger.Debug("flush caches")
|
||||
r.resultCache.Clear()
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ func (r *ClientNamesResolver) Resolve(ctx context.Context, request *model.Reques
|
|||
clientNames := r.getClientNames(ctx, request)
|
||||
|
||||
request.ClientNames = clientNames
|
||||
request.Log = request.Log.WithField("client_names", strings.Join(clientNames, "; "))
|
||||
ctx, _ = log.CtxWithFields(ctx, logrus.Fields{"client_names": strings.Join(clientNames, "; ")})
|
||||
|
||||
return r.next.Resolve(ctx, request)
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func (r *ClientNamesResolver) getClientNames(ctx context.Context, request *model
|
|||
return cpy
|
||||
}
|
||||
|
||||
names := r.resolveClientNames(ctx, ip, log.WithPrefix(request.Log, "client_names_resolver"))
|
||||
names := r.resolveClientNames(ctx, ip)
|
||||
|
||||
r.cache.Put(ip.String(), &names, time.Hour)
|
||||
|
||||
|
@ -111,9 +111,9 @@ func extractClientNamesFromAnswer(answer []dns.RR, fallbackIP net.IP) (clientNam
|
|||
}
|
||||
|
||||
// tries to resolve client name from mapping, performs reverse DNS lookup otherwise
|
||||
func (r *ClientNamesResolver) resolveClientNames(
|
||||
ctx context.Context, ip net.IP, logger *logrus.Entry,
|
||||
) (result []string) {
|
||||
func (r *ClientNamesResolver) resolveClientNames(ctx context.Context, ip net.IP) (result []string) {
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
// try client mapping first
|
||||
result = r.getNameFromIPMapping(ip, result)
|
||||
if len(result) > 0 {
|
||||
|
@ -128,7 +128,6 @@ func (r *ClientNamesResolver) resolveClientNames(
|
|||
|
||||
resp, err := r.externalResolver.Resolve(ctx, &model.Request{
|
||||
Req: util.NewMsgWithQuestion(reverse, dns.Type(dns.TypePTR)),
|
||||
Log: logger,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("can't resolve client name: ", err)
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
|
||||
|
@ -83,7 +82,7 @@ func (r *ConditionalUpstreamResolver) processRequest(
|
|||
|
||||
// Resolve uses the conditional resolver to resolve the query
|
||||
func (r *ConditionalUpstreamResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, "conditional_resolver")
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
if len(r.mapping) > 0 {
|
||||
resolved, resp, err := r.processRequest(ctx, request)
|
||||
|
@ -101,7 +100,7 @@ func (r *ConditionalUpstreamResolver) internalResolve(ctx context.Context, reso
|
|||
req *model.Request,
|
||||
) (*model.Response, error) {
|
||||
// internal request resolution
|
||||
logger := log.WithPrefix(req.Log, "conditional_resolver")
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
req.Req.Question[0].Name = dns.Fqdn(doFQ)
|
||||
response, err := reso.Resolve(ctx, req)
|
||||
|
|
|
@ -2,11 +2,12 @@ package resolver
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
|
||||
|
@ -14,27 +15,54 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type createAnswerFunc func(question dns.Question, ip net.IP, ttl uint32) (dns.RR, error)
|
||||
|
||||
// CustomDNSResolver resolves passed domain name to ip address defined in domain-IP map
|
||||
type CustomDNSResolver struct {
|
||||
configurable[*config.CustomDNS]
|
||||
NextResolver
|
||||
typed
|
||||
|
||||
mapping map[string][]net.IP
|
||||
reverseAddresses map[string][]string
|
||||
createAnswerFromQuestion createAnswerFunc
|
||||
mapping config.CustomDNSMapping
|
||||
reverseAddresses map[string][]string
|
||||
}
|
||||
|
||||
// NewCustomDNSResolver creates new resolver instance
|
||||
func NewCustomDNSResolver(cfg config.CustomDNS) *CustomDNSResolver {
|
||||
m := make(map[string][]net.IP, len(cfg.Mapping.HostIPs))
|
||||
reverse := make(map[string][]string, len(cfg.Mapping.HostIPs))
|
||||
dnsRecords := make(config.CustomDNSMapping, len(cfg.Mapping)+len(cfg.Zone.RRs))
|
||||
|
||||
for url, ips := range cfg.Mapping.HostIPs {
|
||||
m[strings.ToLower(url)] = ips
|
||||
for url, entries := range cfg.Mapping {
|
||||
url = util.ExtractDomainOnly(url)
|
||||
dnsRecords[url] = entries
|
||||
|
||||
for _, ip := range ips {
|
||||
r, _ := dns.ReverseAddr(ip.String())
|
||||
reverse[r] = append(reverse[r], url)
|
||||
for _, entry := range entries {
|
||||
entry.Header().Ttl = cfg.CustomTTL.SecondsU32()
|
||||
}
|
||||
}
|
||||
|
||||
for url, entries := range cfg.Zone.RRs {
|
||||
url = util.ExtractDomainOnly(url)
|
||||
dnsRecords[url] = entries
|
||||
}
|
||||
|
||||
reverse := make(map[string][]string, len(dnsRecords))
|
||||
|
||||
for url, entries := range dnsRecords {
|
||||
for _, entry := range entries {
|
||||
a, isA := entry.(*dns.A)
|
||||
|
||||
if isA {
|
||||
r, _ := dns.ReverseAddr(a.A.String())
|
||||
reverse[r] = append(reverse[r], url)
|
||||
}
|
||||
|
||||
aaaa, isAAAA := entry.(*dns.AAAA)
|
||||
|
||||
if isAAAA {
|
||||
r, _ := dns.ReverseAddr(aaaa.AAAA.String())
|
||||
reverse[r] = append(reverse[r], url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,8 +70,9 @@ func NewCustomDNSResolver(cfg config.CustomDNS) *CustomDNSResolver {
|
|||
configurable: withConfig(&cfg),
|
||||
typed: withType("custom_dns"),
|
||||
|
||||
mapping: m,
|
||||
reverseAddresses: reverse,
|
||||
createAnswerFromQuestion: util.CreateAnswerFromQuestion,
|
||||
mapping: dnsRecords,
|
||||
reverseAddresses: reverse,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,9 +104,12 @@ func (r *CustomDNSResolver) handleReverseDNS(request *model.Request) *model.Resp
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *CustomDNSResolver) processRequest(request *model.Request) *model.Response {
|
||||
logger := log.WithPrefix(request.Log, "custom_dns_resolver")
|
||||
|
||||
func (r *CustomDNSResolver) processRequest(
|
||||
ctx context.Context,
|
||||
logger *logrus.Entry,
|
||||
request *model.Request,
|
||||
resolvedCnames []string,
|
||||
) (*model.Response, error) {
|
||||
response := new(dns.Msg)
|
||||
response.SetReply(request.Req)
|
||||
|
||||
|
@ -85,13 +117,20 @@ func (r *CustomDNSResolver) processRequest(request *model.Request) *model.Respon
|
|||
domain := util.ExtractDomain(question)
|
||||
|
||||
for len(domain) > 0 {
|
||||
ips, found := r.mapping[domain]
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entries, found := r.mapping[domain]
|
||||
|
||||
if found {
|
||||
for _, ip := range ips {
|
||||
if isSupportedType(ip, question) {
|
||||
rr, _ := util.CreateAnswerFromQuestion(question, ip, r.cfg.CustomTTL.SecondsU32())
|
||||
response.Answer = append(response.Answer, rr)
|
||||
for _, entry := range entries {
|
||||
result, err := r.processDNSEntry(ctx, logger, request, resolvedCnames, question, entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response.Answer = append(response.Answer, result...)
|
||||
}
|
||||
|
||||
if len(response.Answer) > 0 {
|
||||
|
@ -100,7 +139,7 @@ func (r *CustomDNSResolver) processRequest(request *model.Request) *model.Respon
|
|||
"domain": domain,
|
||||
}).Debugf("returning custom dns entry")
|
||||
|
||||
return &model.Response{Res: response, RType: model.ResponseTypeCUSTOMDNS, Reason: "CUSTOM DNS"}
|
||||
return &model.Response{Res: response, RType: model.ResponseTypeCUSTOMDNS, Reason: "CUSTOM DNS"}, nil
|
||||
}
|
||||
|
||||
// Mapping exists for this domain, but for another type
|
||||
|
@ -110,36 +149,110 @@ func (r *CustomDNSResolver) processRequest(request *model.Request) *model.Respon
|
|||
}
|
||||
|
||||
// return NOERROR with empty result
|
||||
return &model.Response{Res: response, RType: model.ResponseTypeCUSTOMDNS, Reason: "CUSTOM DNS"}
|
||||
return &model.Response{Res: response, RType: model.ResponseTypeCUSTOMDNS, Reason: "CUSTOM DNS"}, nil
|
||||
}
|
||||
|
||||
if i := strings.Index(domain, "."); i >= 0 {
|
||||
if i := strings.IndexRune(domain, '.'); i >= 0 {
|
||||
domain = domain[i+1:]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve uses internal mapping to resolve the query
|
||||
func (r *CustomDNSResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, "custom_dns_resolver")
|
||||
|
||||
reverseResp := r.handleReverseDNS(request)
|
||||
if reverseResp != nil {
|
||||
return reverseResp, nil
|
||||
}
|
||||
|
||||
if len(r.mapping) > 0 {
|
||||
resp := r.processRequest(request)
|
||||
if resp != nil {
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
logger.WithField("next_resolver", Name(r.next)).Trace("go to next resolver")
|
||||
|
||||
return r.next.Resolve(ctx, request)
|
||||
}
|
||||
|
||||
func (r *CustomDNSResolver) processDNSEntry(
|
||||
ctx context.Context,
|
||||
logger *logrus.Entry,
|
||||
request *model.Request,
|
||||
resolvedCnames []string,
|
||||
question dns.Question,
|
||||
entry dns.RR,
|
||||
) ([]dns.RR, error) {
|
||||
switch v := entry.(type) {
|
||||
case *dns.A:
|
||||
return r.processIP(v.A, question, v.Header().Ttl)
|
||||
case *dns.AAAA:
|
||||
return r.processIP(v.AAAA, question, v.Header().Ttl)
|
||||
case *dns.CNAME:
|
||||
return r.processCNAME(ctx, logger, request, *v, resolvedCnames, question, v.Header().Ttl)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsupported customDNS RR type %T", entry)
|
||||
}
|
||||
|
||||
// Resolve uses internal mapping to resolve the query
|
||||
func (r *CustomDNSResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
reverseResp := r.handleReverseDNS(request)
|
||||
if reverseResp != nil {
|
||||
return reverseResp, nil
|
||||
}
|
||||
|
||||
return r.processRequest(ctx, logger, request, make([]string, 0, len(r.cfg.Mapping)))
|
||||
}
|
||||
|
||||
func (r *CustomDNSResolver) processIP(ip net.IP, question dns.Question, ttl uint32) (result []dns.RR, err error) {
|
||||
result = make([]dns.RR, 0)
|
||||
|
||||
if isSupportedType(ip, question) {
|
||||
rr, err := r.createAnswerFromQuestion(question, ip, ttl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, rr)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *CustomDNSResolver) processCNAME(
|
||||
ctx context.Context,
|
||||
logger *logrus.Entry,
|
||||
request *model.Request,
|
||||
targetCname dns.CNAME,
|
||||
resolvedCnames []string,
|
||||
question dns.Question,
|
||||
ttl uint32,
|
||||
) (result []dns.RR, err error) {
|
||||
cname := new(dns.CNAME)
|
||||
cname.Hdr = dns.RR_Header{Class: dns.ClassINET, Ttl: ttl, Rrtype: dns.TypeCNAME, Name: question.Name}
|
||||
cname.Target = dns.Fqdn(targetCname.Target)
|
||||
result = append(result, cname)
|
||||
|
||||
if question.Qtype == dns.TypeCNAME {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
targetWithoutDot := strings.TrimSuffix(targetCname.Target, ".")
|
||||
|
||||
if slices.Contains(resolvedCnames, targetWithoutDot) {
|
||||
return nil, fmt.Errorf("CNAME loop detected: %v", append(resolvedCnames, targetWithoutDot))
|
||||
}
|
||||
|
||||
cnames := resolvedCnames
|
||||
cnames = append(cnames, targetWithoutDot)
|
||||
|
||||
clientIP := request.ClientIP.String()
|
||||
clientID := request.RequestClientID
|
||||
targetRequest := newRequestWithClientID(targetWithoutDot, dns.Type(question.Qtype), clientIP, clientID)
|
||||
|
||||
// resolve the target recursively
|
||||
targetResp, err := r.processRequest(ctx, logger, targetRequest, cnames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, targetResp.Res.Answer...)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *CustomDNSResolver) CreateAnswerFromQuestion(newFunc createAnswerFunc) {
|
||||
r.createAnswerFromQuestion = newFunc
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package resolver
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
|
@ -17,7 +18,8 @@ import (
|
|||
|
||||
var _ = Describe("CustomDNSResolver", func() {
|
||||
var (
|
||||
TTL = uint32(time.Now().Second())
|
||||
TTL = uint32(time.Now().Second())
|
||||
zoneTTL = uint32(time.Now().Second() * 2)
|
||||
|
||||
sut *CustomDNSResolver
|
||||
m *mockResolver
|
||||
|
@ -37,16 +39,28 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
ctx, cancelFn = context.WithCancel(context.Background())
|
||||
DeferCleanup(cancelFn)
|
||||
|
||||
zoneHdr := dns.RR_Header{Ttl: zoneTTL}
|
||||
|
||||
cfg = config.CustomDNS{
|
||||
Mapping: config.CustomDNSMapping{HostIPs: map[string][]net.IP{
|
||||
"custom.domain": {net.ParseIP("192.168.143.123")},
|
||||
"ip6.domain": {net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
Mapping: config.CustomDNSMapping{
|
||||
"custom.domain": {&dns.A{A: net.ParseIP("192.168.143.123")}},
|
||||
"ip6.domain": {&dns.AAAA{AAAA: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")}},
|
||||
"multiple.ips": {
|
||||
net.ParseIP("192.168.143.123"),
|
||||
net.ParseIP("192.168.143.125"),
|
||||
net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
&dns.A{A: net.ParseIP("192.168.143.123")},
|
||||
&dns.A{A: net.ParseIP("192.168.143.125")},
|
||||
&dns.AAAA{AAAA: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")},
|
||||
},
|
||||
}},
|
||||
},
|
||||
Zone: config.ZoneFileDNS{
|
||||
RRs: config.CustomDNSMapping{
|
||||
"example.zone.": {&dns.A{A: net.ParseIP("1.2.3.4"), Hdr: zoneHdr}},
|
||||
"cname.domain.": {&dns.CNAME{Target: "custom.domain", Hdr: zoneHdr}},
|
||||
"cname.ip6.": {&dns.CNAME{Target: "ip6.domain", Hdr: zoneHdr}},
|
||||
"cname.example.": {&dns.CNAME{Target: "example.com", Hdr: zoneHdr}},
|
||||
"cname.recursive.": {&dns.CNAME{Target: "cname.recursive", Hdr: zoneHdr}},
|
||||
"mx.domain.": {&dns.MX{Mx: "mx.domain", Hdr: zoneHdr}},
|
||||
},
|
||||
},
|
||||
CustomTTL: config.Duration(time.Duration(TTL) * time.Second),
|
||||
FilterUnmappedTypes: true,
|
||||
}
|
||||
|
@ -76,9 +90,73 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
})
|
||||
|
||||
Describe("Resolving custom name via CustomDNSResolver", func() {
|
||||
When("The parent context has an error ", func() {
|
||||
It("should return the error", func() {
|
||||
cancelledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
_, err := sut.Resolve(cancelledCtx, newRequest("custom.domain.", A))
|
||||
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("context canceled"))
|
||||
})
|
||||
})
|
||||
When("Creating the IP response returns an error ", func() {
|
||||
It("should return the error", func() {
|
||||
createAnswerMock := func(_ dns.Question, _ net.IP, _ uint32) (dns.RR, error) {
|
||||
return nil, fmt.Errorf("create answer error")
|
||||
}
|
||||
|
||||
sut.CreateAnswerFromQuestion(createAnswerMock)
|
||||
|
||||
_, err := sut.Resolve(ctx, newRequest("custom.domain.", A))
|
||||
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("create answer error"))
|
||||
})
|
||||
})
|
||||
When("The forward request returns an error ", func() {
|
||||
It("should return the error if the error occurs when checking ipv4 forward addresses", func() {
|
||||
err := fmt.Errorf("forward error")
|
||||
m = &mockResolver{}
|
||||
|
||||
m.On("Resolve", mock.Anything).Return(nil, err)
|
||||
|
||||
sut.Next(m)
|
||||
_, err = sut.Resolve(ctx, newRequest("cname.example.", A))
|
||||
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("forward error"))
|
||||
})
|
||||
It("should return the error if the error occurs when checking ipv6 forward addresses", func() {
|
||||
err := fmt.Errorf("forward error")
|
||||
m = &mockResolver{}
|
||||
|
||||
m.On("Resolve", mock.Anything).Return(nil, err)
|
||||
|
||||
sut.Next(m)
|
||||
_, err = sut.Resolve(ctx, newRequest("cname.example.", AAAA))
|
||||
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("forward error"))
|
||||
})
|
||||
})
|
||||
When("Ip 4 mapping is defined for custom domain and", func() {
|
||||
Context("filterUnmappedTypes is true", func() {
|
||||
BeforeEach(func() { cfg.FilterUnmappedTypes = true })
|
||||
It("defined ip4 query should be resolved from zone mappings and should use the TTL defined in the zone", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("example.zone.", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
BeDNSRecord("example.zone.", A, "1.2.3.4"),
|
||||
HaveTTL(BeNumerically("==", zoneTTL)),
|
||||
HaveResponseType(ResponseTypeCUSTOMDNS),
|
||||
HaveReason("CUSTOM DNS"),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
It("defined ip4 query should be resolved", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("custom.domain.", A))).
|
||||
Should(
|
||||
|
@ -211,6 +289,101 @@ var _ = Describe("CustomDNSResolver", func() {
|
|||
})
|
||||
})
|
||||
})
|
||||
When("A CNAME record is defined for custom domain ", func() {
|
||||
It("should not recurse if the request is strictly a CNAME request", func() {
|
||||
By("CNAME query", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("cname.domain", CNAME))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
WithTransform(ToAnswer, SatisfyAll(
|
||||
HaveLen(1),
|
||||
ContainElements(
|
||||
BeDNSRecord("cname.domain.", CNAME, "custom.domain.")),
|
||||
)),
|
||||
HaveResponseType(ResponseTypeCUSTOMDNS),
|
||||
HaveReason("CUSTOM DNS"),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
})
|
||||
It("all CNAMES for the current type should be recursively resolved when relying on other Mappings", func() {
|
||||
By("A query", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("cname.domain", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
WithTransform(ToAnswer, SatisfyAll(
|
||||
HaveLen(2),
|
||||
ContainElements(
|
||||
BeDNSRecord("cname.domain.", CNAME, "custom.domain."),
|
||||
BeDNSRecord("custom.domain.", A, "192.168.143.123")),
|
||||
)),
|
||||
HaveResponseType(ResponseTypeCUSTOMDNS),
|
||||
HaveReason("CUSTOM DNS"),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
By("AAAA query", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("cname.ip6", AAAA))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
WithTransform(ToAnswer, SatisfyAll(
|
||||
HaveLen(2),
|
||||
ContainElements(
|
||||
BeDNSRecord("cname.ip6.", CNAME, "ip6.domain."),
|
||||
BeDNSRecord("ip6.domain.", AAAA, "2001:db8:85a3::8a2e:370:7334")),
|
||||
)),
|
||||
HaveResponseType(ResponseTypeCUSTOMDNS),
|
||||
HaveReason("CUSTOM DNS"),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
})
|
||||
It("should return an error when the CNAME is recursive", func() {
|
||||
By("CNAME query", func() {
|
||||
_, err := sut.Resolve(ctx, newRequest("cname.recursive", A))
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("CNAME loop detected:"))
|
||||
// will not delegate to next resolver
|
||||
m.AssertNotCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
})
|
||||
It("all CNAMES for the current type should be returned when relying on public DNS", func() {
|
||||
By("CNAME query", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("cname.example", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
WithTransform(ToAnswer, SatisfyAll(
|
||||
ContainElements(
|
||||
BeDNSRecord("cname.example.", CNAME, "example.com.")),
|
||||
)),
|
||||
HaveResponseType(ResponseTypeCUSTOMDNS),
|
||||
HaveReason("CUSTOM DNS"),
|
||||
HaveReturnCode(dns.RcodeSuccess),
|
||||
))
|
||||
|
||||
// will delegate to next resolver
|
||||
m.AssertCalled(GinkgoT(), "Resolve", mock.Anything)
|
||||
})
|
||||
})
|
||||
})
|
||||
When("An unsupported DNS query type is queried from the resolver but found in the config mapping ", func() {
|
||||
It("an error should be returned", func() {
|
||||
By("MX query", func() {
|
||||
_, err := sut.Resolve(ctx, newRequest("mx.domain", MX))
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).Should(ContainSubstring("unsupported customDNS RR type *dns.MX"))
|
||||
})
|
||||
})
|
||||
})
|
||||
When("Reverse DNS request is received", func() {
|
||||
It("should resolve the defined domain name", func() {
|
||||
By("ipv4", func() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc7871.html#section-6
|
||||
|
@ -49,23 +50,26 @@ func NewECSResolver(cfg config.ECS) ChainedResolver {
|
|||
// and sets the client IP from the EDNS0 option to the request if this option is enabled
|
||||
func (r *ECSResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
if r.cfg.IsEnabled() {
|
||||
ctx, logger := r.log(ctx)
|
||||
_ = ctx
|
||||
|
||||
so := util.GetEdns0Option[*dns.EDNS0_SUBNET](request.Req)
|
||||
// Set the client IP from the Edns0 subnet option if the option is enabled and the correct subnet mask is set
|
||||
if r.cfg.UseAsClient && so != nil && ((so.Family == ecsFamilyIPv4 && so.SourceNetmask == ecsMaskIPv4) ||
|
||||
(so.Family == ecsFamilyIPv6 && so.SourceNetmask == ecsMaskIPv6)) {
|
||||
request.Log.Debugf("using request's edns0 address as internal client IP: %s", so.Address)
|
||||
logger.Debugf("using request's edns0 address as internal client IP: %s", so.Address)
|
||||
request.ClientIP = so.Address
|
||||
}
|
||||
|
||||
// Set the Edns0 subnet option if the client IP is IPv4 or IPv6 and the masks are set in the configuration
|
||||
if r.cfg.IPv4Mask > 0 || r.cfg.IPv6Mask > 0 {
|
||||
r.setSubnet(so, request)
|
||||
r.setSubnet(so, request, logger)
|
||||
}
|
||||
|
||||
// Remove the Edns0 subnet option if the client IP is IPv4 or IPv6 and the corresponding mask is not set
|
||||
// and the forwardEcs option is not enabled
|
||||
if r.cfg.IPv4Mask == 0 && r.cfg.IPv6Mask == 0 && so != nil && !r.cfg.Forward {
|
||||
request.Log.Debug("remove edns0 subnet option")
|
||||
logger.Debug("remove edns0 subnet option")
|
||||
util.RemoveEdns0Option[*dns.EDNS0_SUBNET](request.Req)
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +79,7 @@ func (r *ECSResolver) Resolve(ctx context.Context, request *model.Request) (*mod
|
|||
|
||||
// setSubnet appends the subnet information to the request as EDNS0 option
|
||||
// if the client IP is IPv4 or IPv6 and the corresponding mask is set in the configuration
|
||||
func (r *ECSResolver) setSubnet(so *dns.EDNS0_SUBNET, request *model.Request) {
|
||||
func (r *ECSResolver) setSubnet(so *dns.EDNS0_SUBNET, request *model.Request, logger *logrus.Entry) {
|
||||
var subIP net.IP
|
||||
if so != nil && r.cfg.Forward && so.Address != nil {
|
||||
subIP = so.Address
|
||||
|
@ -96,7 +100,7 @@ func (r *ECSResolver) setSubnet(so *dns.EDNS0_SUBNET, request *model.Request) {
|
|||
}
|
||||
|
||||
if edsOption != nil {
|
||||
request.Log.Debugf("set edns0 subnet option address: %s", edsOption.Address)
|
||||
logger.Debugf("set edns0 subnet option address: %s", edsOption.Address)
|
||||
util.SetEdns0Option(request.Req, edsOption)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@ func NewHostsFileResolver(ctx context.Context,
|
|||
}
|
||||
|
||||
err := cfg.Loading.StartPeriodicRefresh(ctx, r.loadSources, func(err error) {
|
||||
r.log().WithError(err).Errorf("could not load hosts files")
|
||||
_, logger := r.log(ctx)
|
||||
logger.WithError(err).Errorf("could not load hosts files")
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -114,6 +115,8 @@ func (r *HostsFileResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
return r.next.Resolve(ctx, request)
|
||||
}
|
||||
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
reverseResp := r.handleReverseDNS(request)
|
||||
if reverseResp != nil {
|
||||
return reverseResp, nil
|
||||
|
@ -124,7 +127,7 @@ func (r *HostsFileResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
|
||||
response := r.resolve(request.Req, question, domain)
|
||||
if response != nil {
|
||||
r.log().WithFields(logrus.Fields{
|
||||
logger.WithFields(logrus.Fields{
|
||||
"answer": util.AnswerToString(response.Answer),
|
||||
"domain": util.Obfuscate(domain),
|
||||
}).Debugf("returning hosts file entry")
|
||||
|
@ -132,7 +135,7 @@ func (r *HostsFileResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
return &model.Response{Res: response, RType: model.ResponseTypeHOSTSFILE, Reason: "HOSTS FILE"}, nil
|
||||
}
|
||||
|
||||
r.log().WithField("next_resolver", Name(r.next)).Trace("go to next resolver")
|
||||
logger.WithField("next_resolver", Name(r.next)).Trace("go to next resolver")
|
||||
|
||||
return r.next.Resolve(ctx, request)
|
||||
}
|
||||
|
@ -157,7 +160,9 @@ func (r *HostsFileResolver) loadSources(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
r.log().Debug("loading hosts files")
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
logger.Debug("loading hosts files")
|
||||
|
||||
//nolint:ineffassign,staticcheck,wastedassign // keep `ctx :=` so if we use ctx in the future, we use the correct one
|
||||
consumersGrp, ctx := jobgroup.WithContext(ctx)
|
||||
|
@ -220,7 +225,9 @@ func (r *HostsFileResolver) parseFile(
|
|||
|
||||
p := parsers.AllowErrors(parsers.HostsFile(reader), r.cfg.Loading.MaxErrorsPerSource)
|
||||
p.OnErr(func(err error) {
|
||||
r.log().Warnf("error parsing %s: %s, trying to continue", opener, err)
|
||||
_, logger := r.log(ctx)
|
||||
|
||||
logger.Warnf("error parsing %s: %s, trying to continue", opener, err)
|
||||
})
|
||||
|
||||
return parsers.ForEach[*HostsFileEntry](ctx, p, func(entry *HostsFileEntry) error {
|
||||
|
|
|
@ -173,6 +173,7 @@ func newTestDOHUpstream(fn func(request *dns.Msg) (response *dns.Msg),
|
|||
f(w)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = w.Write(b)
|
||||
|
||||
util.FatalOnError("can't write response: ", err)
|
||||
|
|
|
@ -21,6 +21,11 @@ func (NoOpResolver) Type() string {
|
|||
return "noop"
|
||||
}
|
||||
|
||||
// String implements `fmt.Stringer`.
|
||||
func (r NoOpResolver) String() string {
|
||||
return r.Type()
|
||||
}
|
||||
|
||||
// IsEnabled implements `config.Configurable`.
|
||||
func (NoOpResolver) IsEnabled() bool {
|
||||
return true
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -140,7 +141,7 @@ func (r *ParallelBestResolver) String() string {
|
|||
|
||||
upstreams := make([]string, len(resolvers))
|
||||
for i, s := range resolvers {
|
||||
upstreams[i] = fmt.Sprintf("%s", s.resolver)
|
||||
upstreams[i] = s.resolver.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s upstreams '%s (%s)'", r.Type(), r.cfg.Name, strings.Join(upstreams, ","))
|
||||
|
@ -148,7 +149,7 @@ func (r *ParallelBestResolver) String() string {
|
|||
|
||||
// Resolve sends the query request to multiple upstream resolvers and returns the fastest result
|
||||
func (r *ParallelBestResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, parallelResolverType)
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
allResolvers := *r.resolvers.Load()
|
||||
|
||||
|
@ -162,7 +163,7 @@ func (r *ParallelBestResolver) Resolve(ctx context.Context, request *model.Reque
|
|||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel() // abort requests to resolvers that lost the race
|
||||
|
||||
resolvers := pickRandom(allResolvers, r.resolverCount)
|
||||
resolvers := pickRandom(ctx, allResolvers, r.resolverCount)
|
||||
ch := make(chan requestResponse, len(resolvers))
|
||||
|
||||
for _, resolver := range resolvers {
|
||||
|
@ -211,7 +212,7 @@ func (r *ParallelBestResolver) retryWithDifferent(
|
|||
ctx context.Context, logger *logrus.Entry, request *model.Request, resolvers []*upstreamResolverStatus,
|
||||
) (*model.Response, error) {
|
||||
// second try (if retryWithDifferentResolver == true)
|
||||
resolver := weightedRandom(*r.resolvers.Load(), resolvers)
|
||||
resolver := weightedRandom(ctx, *r.resolvers.Load(), resolvers)
|
||||
logger.Debugf("using %s as second resolver", resolver.resolver)
|
||||
|
||||
resp, err := resolver.resolve(ctx, request)
|
||||
|
@ -228,17 +229,17 @@ func (r *ParallelBestResolver) retryWithDifferent(
|
|||
}
|
||||
|
||||
// pickRandom picks n (resolverCount) different random resolvers from the given resolver pool
|
||||
func pickRandom(resolvers []*upstreamResolverStatus, resolverCount int) []*upstreamResolverStatus {
|
||||
func pickRandom(ctx context.Context, resolvers []*upstreamResolverStatus, resolverCount int) []*upstreamResolverStatus {
|
||||
chosenResolvers := make([]*upstreamResolverStatus, 0, resolverCount)
|
||||
|
||||
for i := 0; i < resolverCount; i++ {
|
||||
chosenResolvers = append(chosenResolvers, weightedRandom(resolvers, chosenResolvers))
|
||||
chosenResolvers = append(chosenResolvers, weightedRandom(ctx, resolvers, chosenResolvers))
|
||||
}
|
||||
|
||||
return chosenResolvers
|
||||
}
|
||||
|
||||
func weightedRandom(in, excludedResolvers []*upstreamResolverStatus) *upstreamResolverStatus {
|
||||
func weightedRandom(ctx context.Context, in, excludedResolvers []*upstreamResolverStatus) *upstreamResolverStatus {
|
||||
const errorWindowInSec = 60
|
||||
|
||||
choices := make([]weightedrand.Choice[*upstreamResolverStatus, uint], 0, len(in))
|
||||
|
@ -263,7 +264,13 @@ outer:
|
|||
}
|
||||
|
||||
c, err := weightedrand.NewChooser(choices...)
|
||||
util.LogOnError("can't choose random weighted resolver: ", err)
|
||||
if err != nil {
|
||||
log.FromCtx(ctx).WithError(err).Error("can't choose random weighted resolver, falling back to uniform random")
|
||||
|
||||
val := rand.Int() //nolint:gosec // pseudo-randomness is good enough
|
||||
|
||||
return choices[val%len(choices)].Item
|
||||
}
|
||||
|
||||
return c.Pick()
|
||||
}
|
||||
|
|
|
@ -301,12 +301,12 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
upstreams = []config.Upstream{withError1, mockUpstream1.Start(), mockUpstream2.Start(), withError2}
|
||||
})
|
||||
|
||||
It("should use 2 random peeked resolvers, weighted with last error timestamp", func() {
|
||||
It("should use 2 random peeked resolvers, weighted with last error timestamp", func(ctx context.Context) {
|
||||
By("all resolvers have same weight for random -> equal distribution", func() {
|
||||
resolverCount := make(map[Resolver]int)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
resolvers := pickRandom(*sut.resolvers.Load(), parallelBestResolverCount)
|
||||
resolvers := pickRandom(ctx, *sut.resolvers.Load(), parallelBestResolverCount)
|
||||
res1 := resolvers[0].resolver
|
||||
res2 := resolvers[1].resolver
|
||||
Expect(res1).ShouldNot(Equal(res2))
|
||||
|
@ -330,7 +330,7 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
resolverCount := make(map[*UpstreamResolver]int)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
resolvers := pickRandom(*sut.resolvers.Load(), parallelBestResolverCount)
|
||||
resolvers := pickRandom(ctx, *sut.resolvers.Load(), parallelBestResolverCount)
|
||||
res1 := resolvers[0].resolver.(*UpstreamResolver)
|
||||
res2 := resolvers[1].resolver.(*UpstreamResolver)
|
||||
Expect(res1).ShouldNot(Equal(res2))
|
||||
|
@ -493,12 +493,12 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
upstreams = []config.Upstream{withError1, mockUpstream1.Start(), mockUpstream2.Start(), withError2}
|
||||
})
|
||||
|
||||
It("should use 2 random peeked resolvers, weighted with last error timestamp", func() {
|
||||
It("should use 2 random peeked resolvers, weighted with last error timestamp", func(ctx context.Context) {
|
||||
By("all resolvers have same weight for random -> equal distribution", func() {
|
||||
resolverCount := make(map[Resolver]int)
|
||||
|
||||
for i := 0; i < 2000; i++ {
|
||||
r := weightedRandom(*sut.resolvers.Load(), nil)
|
||||
r := weightedRandom(ctx, *sut.resolvers.Load(), nil)
|
||||
resolverCount[r.resolver]++
|
||||
}
|
||||
for _, v := range resolverCount {
|
||||
|
@ -517,7 +517,7 @@ var _ = Describe("ParallelBestResolver", Label("parallelBestResolver"), func() {
|
|||
resolverCount := make(map[*UpstreamResolver]int)
|
||||
|
||||
for i := 0; i < 200; i++ {
|
||||
r := weightedRandom(*sut.resolvers.Load(), nil)
|
||||
r := weightedRandom(ctx, *sut.resolvers.Load(), nil)
|
||||
res := r.resolver.(*UpstreamResolver)
|
||||
|
||||
resolverCount[res]++
|
||||
|
|
|
@ -38,6 +38,7 @@ func NewQueryLoggingResolver(ctx context.Context, cfg config.QueryLog) *QueryLog
|
|||
err := retry.Do(
|
||||
func() error {
|
||||
var err error
|
||||
|
||||
switch cfg.Type {
|
||||
case config.QueryLogTypeCsv:
|
||||
writer, err = querylog.NewCSVWriter(cfg.Target, false, cfg.LogRetentionDays)
|
||||
|
@ -112,23 +113,43 @@ func (r *QueryLoggingResolver) doCleanUp() {
|
|||
|
||||
// Resolve logs the query, duration and the result
|
||||
func (r *QueryLoggingResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, queryLoggingResolverType)
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
resp, err := r.next.Resolve(ctx, request)
|
||||
|
||||
duration := time.Since(start).Milliseconds()
|
||||
|
||||
if err == nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry := r.createLogEntry(request, resp, start, duration)
|
||||
|
||||
if r.ignore(resp) {
|
||||
// Log to the console for debugging purposes
|
||||
logger.WithFields(querylog.LogEntryFields(entry)).Debug("ignored querylog entry")
|
||||
} else {
|
||||
select {
|
||||
case r.logChan <- r.createLogEntry(request, resp, start, duration):
|
||||
case r.logChan <- entry:
|
||||
default:
|
||||
logger.Error("query log writer is too slow, log entry will be dropped")
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (r *QueryLoggingResolver) ignore(response *model.Response) bool {
|
||||
cfg := r.cfg.Ignore
|
||||
|
||||
if cfg.SUDN && response.RType == model.ResponseTypeSPECIAL {
|
||||
return true
|
||||
}
|
||||
|
||||
// If we add more ways to ignore entries, it would be nice to log why it's ignored in the debug log
|
||||
// Probably make this func return a (string, bool).
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *QueryLoggingResolver) createLogEntry(request *model.Request, response *model.Response,
|
||||
|
@ -170,6 +191,8 @@ func (r *QueryLoggingResolver) createLogEntry(request *model.Request, response *
|
|||
|
||||
// write entry: if log directory is configured, write to log file
|
||||
func (r *QueryLoggingResolver) writeLog(ctx context.Context) {
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
for {
|
||||
select {
|
||||
case logEntry := <-r.logChan:
|
||||
|
@ -177,12 +200,13 @@ func (r *QueryLoggingResolver) writeLog(ctx context.Context) {
|
|||
|
||||
r.writer.Write(logEntry)
|
||||
|
||||
halfCap := cap(r.logChan) / 2 //nolint:gomnd
|
||||
halfCap := cap(r.logChan) / 2 //nolint:mnd
|
||||
|
||||
// if log channel is > 50% full, this could be a problem with slow writer (external storage over network etc.)
|
||||
if len(r.logChan) > halfCap {
|
||||
r.log().WithField("channel_len",
|
||||
len(r.logChan)).Warnf("query log writer is too slow, write duration: %d ms", time.Since(start).Milliseconds())
|
||||
logger.
|
||||
WithField("channel_len", len(r.logChan)).
|
||||
Warnf("query log writer is too slow, write duration: %d ms", time.Since(start).Milliseconds())
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
. "github.com/0xERR0R/blocky/helpertest"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/querylog"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
. "github.com/0xERR0R/blocky/model"
|
||||
|
@ -21,7 +23,6 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type SlowMockWriter struct {
|
||||
|
@ -43,6 +44,7 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
sutConfig config.QueryLog
|
||||
m *mockResolver
|
||||
tmpDir *TmpFolder
|
||||
mockRType ResponseType
|
||||
mockAnswer *dns.Msg
|
||||
|
||||
ctx context.Context
|
||||
|
@ -59,6 +61,12 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
ctx, cancelFn = context.WithCancel(context.Background())
|
||||
DeferCleanup(cancelFn)
|
||||
|
||||
var err error
|
||||
|
||||
sutConfig, err = config.WithDefaults[config.QueryLog]()
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
mockRType = ResponseTypeRESOLVED
|
||||
mockAnswer = new(dns.Msg)
|
||||
tmpDir = NewTmpFolder("queryLoggingResolver")
|
||||
})
|
||||
|
@ -69,8 +77,15 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
}
|
||||
|
||||
sut = NewQueryLoggingResolver(ctx, sutConfig)
|
||||
m = &mockResolver{}
|
||||
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer, Reason: "reason"}, nil)
|
||||
|
||||
m = &mockResolver{
|
||||
ResolveFn: func(context.Context, *Request) (*Response, error) {
|
||||
return &Response{RType: mockRType, Res: mockAnswer, Reason: "reason"}, nil
|
||||
},
|
||||
}
|
||||
|
||||
m.On("Resolve", mock.Anything).Return(autoAnswer, nil)
|
||||
|
||||
sut.Next(m)
|
||||
})
|
||||
|
||||
|
@ -109,6 +124,54 @@ var _ = Describe("QueryLoggingResolver", func() {
|
|||
m.AssertExpectations(GinkgoT())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ignore", func() {
|
||||
var ignored *log.MockLoggerHook
|
||||
|
||||
JustBeforeEach(func() {
|
||||
// Stop background goroutines
|
||||
cancelFn()
|
||||
|
||||
ctx, cancelFn = context.WithCancel(context.Background())
|
||||
DeferCleanup(cancelFn)
|
||||
|
||||
// Capture ignored logs
|
||||
{
|
||||
var logger *logrus.Entry
|
||||
|
||||
logger, ignored = log.NewMockEntry()
|
||||
ctx, _ = log.NewCtx(ctx, logger)
|
||||
}
|
||||
})
|
||||
|
||||
Describe("SUDN", func() {
|
||||
JustBeforeEach(func() {
|
||||
sut.cfg.Ignore.SUDN = true
|
||||
})
|
||||
|
||||
It("should not log SUDN responses", func() {
|
||||
mockRType = ResponseTypeSPECIAL
|
||||
|
||||
_, err := sut.Resolve(ctx, newRequestWithClient("example.com.", A, "192.168.178.25", "client1"))
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
Expect(sut.logChan).Should(BeEmpty())
|
||||
Expect(ignored.Calls).Should(HaveLen(1))
|
||||
Expect(ignored.Messages).Should(ContainElement(ContainSubstring("ignored querylog entry")))
|
||||
})
|
||||
|
||||
It("should log other responses", func() {
|
||||
mockRType = ResponseTypeBLOCKED
|
||||
|
||||
_, err := sut.Resolve(ctx, newRequestWithClient("example.com.", A, "192.168.178.25", "client1"))
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
Expect(sut.logChan).ShouldNot(BeEmpty())
|
||||
Expect(ignored.Calls).Should(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("Configuration with logging per client", func() {
|
||||
BeforeEach(func() {
|
||||
sutConfig = config.QueryLog{
|
||||
|
|
|
@ -15,17 +15,9 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func newRequest(question string, rType dns.Type, logger ...*logrus.Entry) *model.Request {
|
||||
var loggerEntry *logrus.Entry
|
||||
if len(logger) == 1 {
|
||||
loggerEntry = logger[0]
|
||||
} else {
|
||||
loggerEntry = logrus.NewEntry(log.Log())
|
||||
}
|
||||
|
||||
func newRequest(question string, rType dns.Type) *model.Request {
|
||||
return &model.Request{
|
||||
Req: util.NewMsgWithQuestion(question, rType),
|
||||
Log: loggerEntry,
|
||||
Protocol: model.RequestProtocolUDP,
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +27,6 @@ func newRequestWithClient(question string, rType dns.Type, ip string, clientName
|
|||
ClientIP: net.ParseIP(ip),
|
||||
ClientNames: clientNames,
|
||||
Req: util.NewMsgWithQuestion(question, rType),
|
||||
Log: logrus.NewEntry(log.Log()),
|
||||
RequestTS: time.Time{},
|
||||
Protocol: model.RequestProtocolUDP,
|
||||
}
|
||||
|
@ -59,7 +50,6 @@ func newRequestWithClientID(question string, rType dns.Type, ip, requestClientID
|
|||
ClientIP: net.ParseIP(ip),
|
||||
RequestClientID: requestClientID,
|
||||
Req: util.NewMsgWithQuestion(question, rType),
|
||||
Log: logrus.NewEntry(log.Log()),
|
||||
RequestTS: time.Time{},
|
||||
Protocol: model.RequestProtocolUDP,
|
||||
}
|
||||
|
@ -68,6 +58,7 @@ func newRequestWithClientID(question string, rType dns.Type, ip, requestClientID
|
|||
// Resolver generic interface for all resolvers
|
||||
type Resolver interface {
|
||||
config.Configurable
|
||||
fmt.Stringer
|
||||
|
||||
// Type returns a short, user-friendly, name for the resolver.
|
||||
//
|
||||
|
@ -193,8 +184,27 @@ func (t *typed) Type() string {
|
|||
return t.typeName
|
||||
}
|
||||
|
||||
func (t *typed) log() *logrus.Entry {
|
||||
return log.PrefixedLog(t.Type())
|
||||
// String implements `fmt.Stringer`.
|
||||
func (t *typed) String() string {
|
||||
return t.Type()
|
||||
}
|
||||
|
||||
func (t *typed) log(ctx context.Context) (context.Context, *logrus.Entry) {
|
||||
return t.logWith(ctx, func(logger *logrus.Entry) *logrus.Entry { return logger })
|
||||
}
|
||||
|
||||
func (t *typed) logWithFields(ctx context.Context, fields logrus.Fields) (context.Context, *logrus.Entry) {
|
||||
return t.logWith(ctx, func(logger *logrus.Entry) *logrus.Entry {
|
||||
return logger.WithFields(fields)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *typed) logWith(ctx context.Context, wrap func(*logrus.Entry) *logrus.Entry) (context.Context, *logrus.Entry) {
|
||||
return log.WrapCtx(ctx, func(logger *logrus.Entry) *logrus.Entry {
|
||||
logger = log.WithPrefix(logger, t.Type())
|
||||
|
||||
return wrap(logger)
|
||||
})
|
||||
}
|
||||
|
||||
// Should be embedded in a Resolver to auto-implement `config.Configurable`.
|
||||
|
@ -217,7 +227,7 @@ func (c *configurable[T]) LogConfig(logger *logrus.Entry) {
|
|||
}
|
||||
|
||||
type initializable interface {
|
||||
log() *logrus.Entry
|
||||
log(context.Context) (context.Context, *logrus.Entry)
|
||||
setResolvers([]*upstreamResolverStatus)
|
||||
}
|
||||
|
||||
|
@ -236,7 +246,9 @@ func initGroupResolvers[T initializable](
|
|||
}
|
||||
|
||||
onErr := func(err error) {
|
||||
r.log().WithError(err).Error("upstream verification error, will continue to use bootstrap DNS")
|
||||
_, logger := r.log(ctx)
|
||||
|
||||
logger.WithError(err).Error("upstream verification error, will continue to use bootstrap DNS")
|
||||
}
|
||||
|
||||
err := cfg.Init.Strategy.Do(ctx, init, onErr)
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
|
||||
|
@ -59,7 +58,7 @@ func (r *RewriterResolver) LogConfig(logger *logrus.Entry) {
|
|||
|
||||
// Resolve uses the inner resolver to resolve the rewritten query
|
||||
func (r *RewriterResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, "rewriter_resolver")
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
original := request.Req
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
|
||||
|
@ -66,7 +65,7 @@ func (r *StrictResolver) String() string {
|
|||
|
||||
upstreams := make([]string, len(resolvers))
|
||||
for i, s := range resolvers {
|
||||
upstreams[i] = fmt.Sprintf("%s", s.resolver)
|
||||
upstreams[i] = s.resolver.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s upstreams '%s (%s)'", strictResolverType, r.cfg.Name, strings.Join(upstreams, ","))
|
||||
|
@ -74,7 +73,7 @@ func (r *StrictResolver) String() string {
|
|||
|
||||
// Resolve sends the query request in a strict order to the upstream resolvers
|
||||
func (r *StrictResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, strictResolverType)
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
// start with first resolver
|
||||
for _, resolver := range *r.resolvers.Load() {
|
||||
|
|
|
@ -28,6 +28,15 @@ const (
|
|||
retryAttempts = 3
|
||||
)
|
||||
|
||||
// UpstreamServerError wraps a response with RCode ServFail so no other resolver tries to use it.
|
||||
type UpstreamServerError struct {
|
||||
Msg *dns.Msg
|
||||
}
|
||||
|
||||
func (e *UpstreamServerError) Error() string {
|
||||
return "upstream server failed"
|
||||
}
|
||||
|
||||
type upstreamConfig struct {
|
||||
config.Upstreams
|
||||
config.Upstream
|
||||
|
@ -89,13 +98,13 @@ func createUpstreamClient(cfg upstreamConfig) upstreamClient {
|
|||
|
||||
switch cfg.Net {
|
||||
case config.NetProtocolHttps:
|
||||
transport := util.DefaultHTTPTransport()
|
||||
transport.TLSClientConfig = &tlsConfig
|
||||
|
||||
return &httpUpstreamClient{
|
||||
userAgent: cfg.UserAgent,
|
||||
client: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tlsConfig,
|
||||
ForceAttemptHTTP2: true,
|
||||
},
|
||||
Transport: transport,
|
||||
},
|
||||
host: cfg.Host,
|
||||
}
|
||||
|
@ -153,7 +162,7 @@ func (r *httpUpstreamClient) callExternal(
|
|||
}
|
||||
|
||||
defer func() {
|
||||
util.LogOnError("can't close response body ", httpResponse.Body.Close())
|
||||
util.LogOnError(ctx, "can't close response body ", httpResponse.Body.Close())
|
||||
}()
|
||||
|
||||
if httpResponse.StatusCode != http.StatusOK {
|
||||
|
@ -195,27 +204,31 @@ func (r *dnsUpstreamClient) callExternal(
|
|||
return r.raceClients(ctx, msg, upstreamURL, protocol)
|
||||
}
|
||||
|
||||
type exchangeResult struct {
|
||||
proto model.RequestProtocol
|
||||
msg *dns.Msg
|
||||
rtt time.Duration
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *dnsUpstreamClient) raceClients(
|
||||
ctx context.Context, msg *dns.Msg, upstreamURL string, protocol model.RequestProtocol,
|
||||
) (response *dns.Msg, rtt time.Duration, err error) {
|
||||
type result struct {
|
||||
proto model.RequestProtocol
|
||||
msg *dns.Msg
|
||||
rtt time.Duration
|
||||
err error
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
// We don't explicitly close the channel, but since the buffer is big enough for all goroutines,
|
||||
// it will be GC'ed and closed automatically.
|
||||
ch := make(chan result, 2) //nolint:gomnd // TCP and UDP
|
||||
ch := make(chan exchangeResult, 2) //nolint:mnd // TCP and UDP
|
||||
|
||||
exchange := func(client *dns.Client, proto model.RequestProtocol) {
|
||||
msg, rtt, err := client.ExchangeContext(ctx, msg, upstreamURL)
|
||||
|
||||
ch <- result{proto, msg, rtt, err}
|
||||
if err == nil && msg.Rcode == dns.RcodeServerFailure {
|
||||
err = &UpstreamServerError{msg}
|
||||
}
|
||||
|
||||
ch <- exchangeResult{proto, msg, rtt, err}
|
||||
}
|
||||
|
||||
go exchange(r.tcpClient, model.RequestProtocolTCP)
|
||||
|
@ -234,7 +247,7 @@ func (r *dnsUpstreamClient) raceClients(
|
|||
return res2.msg, res2.rtt, nil
|
||||
}
|
||||
|
||||
resWhere := func(pred func(*result) bool) *result {
|
||||
resWhere := func(pred func(*exchangeResult) bool) *exchangeResult {
|
||||
if pred(&res1) {
|
||||
return &res1
|
||||
}
|
||||
|
@ -244,13 +257,13 @@ func (r *dnsUpstreamClient) raceClients(
|
|||
|
||||
// When both failed, return the result that used the same protocol as the downstream request
|
||||
if res1.err != nil && res2.err != nil {
|
||||
sameProto := resWhere(func(r *result) bool { return r.proto == protocol })
|
||||
sameProto := resWhere(func(r *exchangeResult) bool { return r.proto == protocol })
|
||||
|
||||
return sameProto.msg, sameProto.rtt, sameProto.err
|
||||
}
|
||||
|
||||
// Only a single one failed, use the one that succeeded
|
||||
successful := resWhere(func(r *result) bool { return r.err == nil })
|
||||
successful := resWhere(func(r *exchangeResult) bool { return r.err == nil })
|
||||
|
||||
return successful.msg, successful.rtt, nil
|
||||
}
|
||||
|
@ -262,7 +275,9 @@ func NewUpstreamResolver(
|
|||
r := newUpstreamResolverUnchecked(cfg, bootstrap)
|
||||
|
||||
onErr := func(err error) {
|
||||
r.log().WithError(err).Warn("initial resolver test failed")
|
||||
_, logger := r.log(ctx)
|
||||
|
||||
logger.WithError(err).Warn("initial resolver test failed")
|
||||
}
|
||||
|
||||
err := cfg.Init.Strategy.Do(ctx, r.testResolve, onErr)
|
||||
|
@ -294,8 +309,10 @@ func (r UpstreamResolver) Upstream() config.Upstream {
|
|||
return r.cfg.Upstream
|
||||
}
|
||||
|
||||
func (r *UpstreamResolver) log() *logrus.Entry {
|
||||
return r.typed.log().WithField("upstream", r.cfg.String())
|
||||
func (r *UpstreamResolver) log(ctx context.Context) (context.Context, *logrus.Entry) {
|
||||
return r.logWithFields(ctx, logrus.Fields{
|
||||
"upstream": r.cfg.String(),
|
||||
})
|
||||
}
|
||||
|
||||
// testResolve sends a test query to verify the upstream is reachable and working
|
||||
|
@ -309,7 +326,9 @@ func (r *UpstreamResolver) testResolve(ctx context.Context) error {
|
|||
}
|
||||
|
||||
// Resolve calls external resolver
|
||||
func (r *UpstreamResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
func (r *UpstreamResolver) Resolve(ctx context.Context, request *model.Request) (response *model.Response, err error) {
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
ips, err := r.bootstrap.UpstreamIPs(ctx, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -334,7 +353,7 @@ func (r *UpstreamResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
}
|
||||
|
||||
resp = response
|
||||
r.logResponse(request, response, ip, rtt)
|
||||
r.logResponse(logger, request, response, ip, rtt)
|
||||
|
||||
return nil
|
||||
},
|
||||
|
@ -345,7 +364,7 @@ func (r *UpstreamResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
retry.LastErrorOnly(true),
|
||||
retry.RetryIf(isTimeout),
|
||||
retry.OnRetry(func(n uint, err error) {
|
||||
r.log().WithFields(logrus.Fields{
|
||||
logger.WithFields(logrus.Fields{
|
||||
"upstream": r.cfg.String(),
|
||||
"upstream_ip": ip.String(),
|
||||
"question": util.QuestionToString(request.Req.Question),
|
||||
|
@ -361,8 +380,10 @@ func (r *UpstreamResolver) Resolve(ctx context.Context, request *model.Request)
|
|||
return &model.Response{Res: resp, Reason: fmt.Sprintf("RESOLVED (%s)", r.cfg)}, nil
|
||||
}
|
||||
|
||||
func (r *UpstreamResolver) logResponse(request *model.Request, resp *dns.Msg, ip net.IP, rtt time.Duration) {
|
||||
r.log().WithFields(logrus.Fields{
|
||||
func (r *UpstreamResolver) logResponse(
|
||||
logger *logrus.Entry, request *model.Request, resp *dns.Msg, ip net.IP, rtt time.Duration,
|
||||
) {
|
||||
logger.WithFields(logrus.Fields{
|
||||
"answer": util.AnswerToString(resp.Answer),
|
||||
"return_code": dns.RcodeToString[resp.Rcode],
|
||||
"upstream": r.cfg.String(),
|
||||
|
|
|
@ -2,8 +2,9 @@ package resolver
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -109,6 +110,20 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
When("Configured DNS resolver returns ServFail", func() {
|
||||
It("should return error", func() {
|
||||
mockUpstream := NewMockUDPUpstreamServer().WithAnswerError(dns.RcodeServerFailure)
|
||||
|
||||
sutConfig.Upstream = mockUpstream.Start()
|
||||
sut := newUpstreamResolverUnchecked(sutConfig, nil)
|
||||
|
||||
_, err := sut.Resolve(ctx, newRequest("example.com.", A))
|
||||
Expect(err).Should(HaveOccurred())
|
||||
|
||||
var servErr *UpstreamServerError
|
||||
Expect(errors.As(err, &servErr)).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
When("Timeout occurs", func() {
|
||||
var counter int32
|
||||
var attemptsWithTimeout int32
|
||||
|
@ -180,7 +195,7 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("Using Dns over HTTP (DOH) upstream", func() {
|
||||
Describe("Using DNS over HTTPS (DoH) upstream", func() {
|
||||
var (
|
||||
respFn func(request *dns.Msg) (response *dns.Msg)
|
||||
modifyHTTPRespFn func(w http.ResponseWriter)
|
||||
|
@ -196,18 +211,34 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
}
|
||||
})
|
||||
|
||||
transport := func() *http.Transport {
|
||||
upstreamClient := sut.upstreamClient.(*httpUpstreamClient)
|
||||
|
||||
return upstreamClient.client.Transport.(*http.Transport)
|
||||
}
|
||||
|
||||
JustBeforeEach(func() {
|
||||
sutConfig.Upstream = newTestDOHUpstream(respFn, modifyHTTPRespFn)
|
||||
sut = newUpstreamResolverUnchecked(sutConfig, nil)
|
||||
|
||||
// use insecure certificates for test doh upstream
|
||||
sut.upstreamClient.(*httpUpstreamClient).client.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
// use insecure certificates for test DoH upstream
|
||||
transport().TLSClientConfig.InsecureSkipVerify = true
|
||||
})
|
||||
When("Configured DOH resolver can resolve query", func() {
|
||||
|
||||
When("a proxy is configured", func() {
|
||||
It("should use it", func() {
|
||||
proxy := TestHTTPProxy()
|
||||
|
||||
transport().Proxy = proxy.ReqURL
|
||||
|
||||
_, err := sut.Resolve(ctx, newRequest("example.com.", A))
|
||||
Expect(err).Should(HaveOccurred())
|
||||
|
||||
upstreamHostPort := net.JoinHostPort(sutConfig.Upstream.Host, fmt.Sprint(sutConfig.Port))
|
||||
Expect(proxy.RequestTarget()).Should(Equal(upstreamHostPort))
|
||||
})
|
||||
})
|
||||
When("Configured DoH resolver can resolve query", func() {
|
||||
It("should return answer from DNS upstream", func() {
|
||||
Expect(sut.Resolve(ctx, newRequest("example.com.", A))).
|
||||
Should(
|
||||
|
@ -220,7 +251,7 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
))
|
||||
})
|
||||
})
|
||||
When("Configured DOH resolver returns wrong http status code", func() {
|
||||
When("Configured DoH resolver returns wrong http status code", func() {
|
||||
BeforeEach(func() {
|
||||
modifyHTTPRespFn = func(w http.ResponseWriter) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
@ -232,7 +263,7 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
Expect(err.Error()).Should(ContainSubstring("http return code should be 200, but received 500"))
|
||||
})
|
||||
})
|
||||
When("Configured DOH resolver returns wrong content type", func() {
|
||||
When("Configured DoH resolver returns wrong content type", func() {
|
||||
BeforeEach(func() {
|
||||
modifyHTTPRespFn = func(w http.ResponseWriter) {
|
||||
w.Header().Set("content-type", "text")
|
||||
|
@ -245,7 +276,7 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
ContainSubstring("http return content type should be 'application/dns-message', but was 'text'"))
|
||||
})
|
||||
})
|
||||
When("Configured DOH resolver returns wrong content", func() {
|
||||
When("Configured DoH resolver returns wrong content", func() {
|
||||
BeforeEach(func() {
|
||||
modifyHTTPRespFn = func(w http.ResponseWriter) {
|
||||
_, _ = w.Write([]byte("wrongcontent"))
|
||||
|
@ -257,7 +288,7 @@ var _ = Describe("UpstreamResolver", Label("upstreamResolver"), func() {
|
|||
Expect(err.Error()).Should(ContainSubstring("can't unpack message"))
|
||||
})
|
||||
})
|
||||
When("Configured DOH resolver does not respond", func() {
|
||||
When("Configured DoH resolver does not respond", func() {
|
||||
JustBeforeEach(func() {
|
||||
sutConfig.Upstream = config.Upstream{
|
||||
Net: config.NetProtocolHttps,
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/0xERR0R/blocky/config"
|
||||
"github.com/0xERR0R/blocky/log"
|
||||
"github.com/0xERR0R/blocky/model"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -106,9 +105,9 @@ func (r *UpstreamTreeResolver) String() string {
|
|||
}
|
||||
|
||||
func (r *UpstreamTreeResolver) Resolve(ctx context.Context, request *model.Request) (*model.Response, error) {
|
||||
logger := log.WithPrefix(request.Log, upstreamTreeResolverType)
|
||||
ctx, logger := r.log(ctx)
|
||||
|
||||
group := r.upstreamGroupByClient(request)
|
||||
group := r.upstreamGroupByClient(logger, request)
|
||||
|
||||
// delegate request to group resolver
|
||||
logger.WithField("resolver", fmt.Sprintf("%s (%s)", group, r.branches[group].Type())).Debug("delegating to resolver")
|
||||
|
@ -116,7 +115,7 @@ func (r *UpstreamTreeResolver) Resolve(ctx context.Context, request *model.Reque
|
|||
return r.branches[group].Resolve(ctx, request)
|
||||
}
|
||||
|
||||
func (r *UpstreamTreeResolver) upstreamGroupByClient(request *model.Request) string {
|
||||
func (r *UpstreamTreeResolver) upstreamGroupByClient(logger *logrus.Entry, request *model.Request) string {
|
||||
groups := make([]string, 0, len(r.branches))
|
||||
clientIP := request.ClientIP.String()
|
||||
|
||||
|
@ -145,7 +144,7 @@ func (r *UpstreamTreeResolver) upstreamGroupByClient(request *model.Request) str
|
|||
|
||||
if len(groups) > 0 {
|
||||
if len(groups) > 1 {
|
||||
request.Log.WithFields(logrus.Fields{
|
||||
logger.WithFields(logrus.Fields{
|
||||
"clientNames": request.ClientNames,
|
||||
"clientIP": clientIP,
|
||||
"groups": groups,
|
||||
|
|
|
@ -269,10 +269,9 @@ var _ = Describe("UpstreamTreeResolver", Label("upstreamTreeResolver"), func() {
|
|||
It("Should use one of the matching resolvers & log warning", func() {
|
||||
logger, hook := log.NewMockEntry()
|
||||
|
||||
request := newRequestWithClient("example.com.", A, "0.0.0.0", "name-matches1")
|
||||
request.Log = logger
|
||||
ctx, _ = log.NewCtx(ctx, logger)
|
||||
|
||||
Expect(sut.Resolve(ctx, request)).
|
||||
Expect(sut.Resolve(ctx, newRequestWithClient("example.com.", A, "0.0.0.0", "name-matches1"))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
SatisfyAny(
|
||||
|
|
185
server/server.go
185
server/server.go
|
@ -9,6 +9,7 @@ import (
|
|||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
|
@ -27,6 +28,7 @@ import (
|
|||
"github.com/0xERR0R/blocky/redis"
|
||||
"github.com/0xERR0R/blocky/resolver"
|
||||
"github.com/0xERR0R/blocky/util"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -416,14 +418,14 @@ func createQueryResolver(
|
|||
}
|
||||
|
||||
func (s *Server) registerDNSHandlers(ctx context.Context) {
|
||||
wrappedOnRequest := func(w dns.ResponseWriter, request *dns.Msg) {
|
||||
s.OnRequest(ctx, w, request)
|
||||
}
|
||||
|
||||
for _, server := range s.dnsServers {
|
||||
handler := server.Handler.(*dns.ServeMux)
|
||||
handler.HandleFunc(".", wrappedOnRequest)
|
||||
handler.HandleFunc("healthcheck.blocky", s.OnHealthCheck)
|
||||
handler.HandleFunc(".", func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
s.OnRequest(ctx, w, m)
|
||||
})
|
||||
handler.HandleFunc("healthcheck.blocky", func(w dns.ResponseWriter, m *dns.Msg) {
|
||||
s.OnHealthCheck(ctx, w, m)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -550,25 +552,6 @@ func (s *Server) Stop(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func createResolverRequest(rw dns.ResponseWriter, request *dns.Msg) *model.Request {
|
||||
var hostName string
|
||||
|
||||
var remoteAddr net.Addr
|
||||
|
||||
if rw != nil {
|
||||
remoteAddr = rw.RemoteAddr()
|
||||
}
|
||||
|
||||
clientIP, protocol := resolveClientIPAndProtocol(remoteAddr)
|
||||
con, ok := rw.(dns.ConnectionStater)
|
||||
|
||||
if ok && con.ConnectionState() != nil {
|
||||
hostName = con.ConnectionState().ServerName
|
||||
}
|
||||
|
||||
return newRequest(clientIP, protocol, extractClientIDFromHost(hostName), request)
|
||||
}
|
||||
|
||||
func extractClientIDFromHost(hostName string) string {
|
||||
const clientIDPrefix = "id-"
|
||||
if strings.HasPrefix(hostName, clientIDPrefix) && strings.Contains(hostName, ".") {
|
||||
|
@ -578,51 +561,138 @@ func extractClientIDFromHost(hostName string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func newRequest(clientIP net.IP, protocol model.RequestProtocol,
|
||||
requestClientID string, request *dns.Msg,
|
||||
) *model.Request {
|
||||
return &model.Request{
|
||||
func newRequest(
|
||||
ctx context.Context,
|
||||
clientIP net.IP, clientID string,
|
||||
protocol model.RequestProtocol, request *dns.Msg,
|
||||
) (context.Context, *model.Request) {
|
||||
ctx, logger := log.CtxWithFields(ctx, logrus.Fields{
|
||||
"req_id": uuid.New().String(),
|
||||
"question": util.QuestionToString(request.Question),
|
||||
"client_ip": clientIP,
|
||||
})
|
||||
|
||||
logger.WithFields(logrus.Fields{
|
||||
"client_request_id": request.Id,
|
||||
"client_id": clientID,
|
||||
"protocol": protocol,
|
||||
}).Trace("new incoming request")
|
||||
|
||||
req := model.Request{
|
||||
ClientIP: clientIP,
|
||||
RequestClientID: requestClientID,
|
||||
RequestClientID: clientID,
|
||||
Protocol: protocol,
|
||||
Req: request,
|
||||
Log: log.Log().WithFields(logrus.Fields{
|
||||
"question": util.QuestionToString(request.Question),
|
||||
"client_ip": clientIP,
|
||||
}),
|
||||
RequestTS: time.Now(),
|
||||
RequestTS: time.Now(),
|
||||
}
|
||||
|
||||
return ctx, &req
|
||||
}
|
||||
|
||||
func newRequestFromDNS(ctx context.Context, rw dns.ResponseWriter, msg *dns.Msg) (context.Context, *model.Request) {
|
||||
var (
|
||||
clientIP net.IP
|
||||
protocol model.RequestProtocol
|
||||
)
|
||||
|
||||
if rw != nil {
|
||||
clientIP, protocol = resolveClientIPAndProtocol(rw.RemoteAddr())
|
||||
}
|
||||
|
||||
var clientID string
|
||||
if con, ok := rw.(dns.ConnectionStater); ok && con.ConnectionState() != nil {
|
||||
clientID = extractClientIDFromHost(con.ConnectionState().ServerName)
|
||||
}
|
||||
|
||||
return newRequest(ctx, clientIP, clientID, protocol, msg)
|
||||
}
|
||||
|
||||
func newRequestFromHTTP(ctx context.Context, req *http.Request, msg *dns.Msg) (context.Context, *model.Request) {
|
||||
protocol := model.RequestProtocolTCP
|
||||
clientIP := util.HTTPClientIP(req)
|
||||
|
||||
clientID := chi.URLParam(req, "clientID")
|
||||
if clientID == "" {
|
||||
clientID = extractClientIDFromHost(req.Host)
|
||||
}
|
||||
|
||||
return newRequest(ctx, clientIP, clientID, protocol, msg)
|
||||
}
|
||||
|
||||
// OnRequest will be executed if a new DNS request is received
|
||||
func (s *Server) OnRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) {
|
||||
logger().Debug("new request")
|
||||
func (s *Server) OnRequest(ctx context.Context, w dns.ResponseWriter, msg *dns.Msg) {
|
||||
ctx, request := newRequestFromDNS(ctx, w, msg)
|
||||
|
||||
r := createResolverRequest(w, request)
|
||||
s.handleReq(ctx, request, w)
|
||||
}
|
||||
|
||||
response, err := s.queryResolver.Resolve(ctx, r)
|
||||
type msgWriter interface {
|
||||
WriteMsg(msg *dns.Msg) error
|
||||
}
|
||||
|
||||
func (s *Server) handleReq(ctx context.Context, request *model.Request, w msgWriter) {
|
||||
response, err := s.resolve(ctx, request)
|
||||
if err != nil {
|
||||
logger().Error("error on processing request:", err)
|
||||
log.FromCtx(ctx).Error("error on processing request:", err)
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(request, dns.RcodeServerFailure)
|
||||
m.SetRcode(request.Req, dns.RcodeServerFailure)
|
||||
err := w.WriteMsg(m)
|
||||
util.LogOnError("can't write message: ", err)
|
||||
util.LogOnError(ctx, "can't write message: ", err)
|
||||
} else {
|
||||
response.Res.MsgHdr.RecursionAvailable = request.MsgHdr.RecursionDesired
|
||||
|
||||
// truncate if necessary
|
||||
response.Res.Truncate(getMaxResponseSize(r))
|
||||
|
||||
// enable compression
|
||||
response.Res.Compress = true
|
||||
|
||||
err := w.WriteMsg(response.Res)
|
||||
util.LogOnError("can't write message: ", err)
|
||||
util.LogOnError(ctx, "can't write message: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) resolve(ctx context.Context, request *model.Request) (response *model.Response, rerr error) {
|
||||
defer func() {
|
||||
if val := recover(); val != nil {
|
||||
rerr = fmt.Errorf("panic occurred: %v", val)
|
||||
}
|
||||
}()
|
||||
|
||||
contextUpstreamTimeoutMultiplier := 100
|
||||
timeoutDuration := time.Duration(contextUpstreamTimeoutMultiplier) * s.cfg.Upstreams.Timeout.ToDuration()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, timeoutDuration)
|
||||
|
||||
defer cancel()
|
||||
|
||||
switch {
|
||||
case len(request.Req.Question) == 0:
|
||||
m := new(dns.Msg)
|
||||
m.SetRcode(request.Req, dns.RcodeFormatError)
|
||||
|
||||
log.FromCtx(ctx).Error("query has no questions")
|
||||
|
||||
response = &model.Response{Res: m, RType: model.ResponseTypeCUSTOMDNS, Reason: "CUSTOM DNS"}
|
||||
default:
|
||||
var err error
|
||||
|
||||
response, err = s.queryResolver.Resolve(ctx, request)
|
||||
if err != nil {
|
||||
var upstreamErr *resolver.UpstreamServerError
|
||||
|
||||
if errors.As(err, &upstreamErr) {
|
||||
response = &model.Response{Res: upstreamErr.Msg, RType: model.ResponseTypeRESOLVED, Reason: upstreamErr.Error()}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
response.Res.MsgHdr.RecursionAvailable = request.Req.MsgHdr.RecursionDesired
|
||||
|
||||
// truncate if necessary
|
||||
response.Res.Truncate(getMaxResponseSize(request))
|
||||
|
||||
// enable compression
|
||||
response.Res.Compress = true
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// returns EDNS UDP size or if not present, 512 for UDP and 64K for TCP
|
||||
func getMaxResponseSize(req *model.Request) int {
|
||||
edns := req.Req.IsEdns0()
|
||||
|
@ -638,20 +708,21 @@ func getMaxResponseSize(req *model.Request) int {
|
|||
}
|
||||
|
||||
// OnHealthCheck Handler for docker health check. Just returns OK code without delegating to resolver chain
|
||||
func (s *Server) OnHealthCheck(w dns.ResponseWriter, request *dns.Msg) {
|
||||
func (s *Server) OnHealthCheck(ctx context.Context, w dns.ResponseWriter, request *dns.Msg) {
|
||||
resp := new(dns.Msg)
|
||||
resp.SetReply(request)
|
||||
resp.Rcode = dns.RcodeSuccess
|
||||
|
||||
err := w.WriteMsg(resp)
|
||||
util.LogOnError("can't write message: ", err)
|
||||
util.LogOnError(ctx, "can't write message: ", err)
|
||||
}
|
||||
|
||||
func resolveClientIPAndProtocol(addr net.Addr) (ip net.IP, protocol model.RequestProtocol) {
|
||||
if t, ok := addr.(*net.UDPAddr); ok {
|
||||
return t.IP, model.RequestProtocolUDP
|
||||
} else if t, ok := addr.(*net.TCPAddr); ok {
|
||||
return t.IP, model.RequestProtocolTCP
|
||||
switch a := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
return a.IP, model.RequestProtocolUDP
|
||||
case *net.TCPAddr:
|
||||
return a.IP, model.RequestProtocolTCP
|
||||
}
|
||||
|
||||
return nil, model.RequestProtocolUDP
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/0xERR0R/blocky/resolver"
|
||||
|
@ -133,9 +132,8 @@ func (s *Server) dohPostRequestHandler(rw http.ResponseWriter, req *http.Request
|
|||
s.processDohMessage(rawMsg, rw, req)
|
||||
}
|
||||
|
||||
func (s *Server) processDohMessage(rawMsg []byte, rw http.ResponseWriter, req *http.Request) {
|
||||
func (s *Server) processDohMessage(rawMsg []byte, rw http.ResponseWriter, httpReq *http.Request) {
|
||||
msg := new(dns.Msg)
|
||||
|
||||
if err := msg.Unpack(rawMsg); err != nil {
|
||||
logger().Error("can't deserialize message: ", err)
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
|
@ -143,61 +141,40 @@ func (s *Server) processDohMessage(rawMsg []byte, rw http.ResponseWriter, req *h
|
|||
return
|
||||
}
|
||||
|
||||
clientID := chi.URLParam(req, "clientID")
|
||||
if clientID == "" {
|
||||
clientID = extractClientIDFromHost(req.Host)
|
||||
}
|
||||
ctx, dnsReq := newRequestFromHTTP(httpReq.Context(), httpReq, msg)
|
||||
|
||||
r := newRequest(net.ParseIP(extractIP(req)), model.RequestProtocolTCP, clientID, msg)
|
||||
|
||||
resResponse, err := s.queryResolver.Resolve(req.Context(), r)
|
||||
if err != nil {
|
||||
logAndResponseWithError(err, "unable to process query: ", rw)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response := new(dns.Msg)
|
||||
response.SetReply(msg)
|
||||
// enable compression
|
||||
resResponse.Res.Compress = true
|
||||
|
||||
b, err := resResponse.Res.Pack()
|
||||
if err != nil {
|
||||
logAndResponseWithError(err, "can't serialize message: ", rw)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rw.Header().Set("content-type", dnsContentType)
|
||||
|
||||
_, err = rw.Write(b)
|
||||
logAndResponseWithError(err, "can't write response: ", rw)
|
||||
s.handleReq(ctx, dnsReq, httpMsgWriter{rw})
|
||||
}
|
||||
|
||||
func extractIP(r *http.Request) string {
|
||||
hostPort := r.Header.Get("X-FORWARDED-FOR")
|
||||
|
||||
if hostPort == "" {
|
||||
hostPort = r.RemoteAddr
|
||||
}
|
||||
|
||||
hostPort = strings.ReplaceAll(hostPort, "[", "")
|
||||
hostPort = strings.ReplaceAll(hostPort, "]", "")
|
||||
index := strings.LastIndex(hostPort, ":")
|
||||
|
||||
if index >= 0 {
|
||||
return hostPort[:index]
|
||||
}
|
||||
|
||||
return hostPort
|
||||
type httpMsgWriter struct {
|
||||
rw http.ResponseWriter
|
||||
}
|
||||
|
||||
func (s *Server) Query(ctx context.Context, question string, qType dns.Type) (*model.Response, error) {
|
||||
dnsRequest := util.NewMsgWithQuestion(question, qType)
|
||||
r := createResolverRequest(nil, dnsRequest)
|
||||
func (r httpMsgWriter) WriteMsg(msg *dns.Msg) error {
|
||||
b, err := msg.Pack()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.queryResolver.Resolve(ctx, r)
|
||||
r.rw.Header().Set("content-type", dnsContentType)
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc8484#section-4.2.1
|
||||
r.rw.WriteHeader(http.StatusOK)
|
||||
|
||||
_, err = r.rw.Write(b)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) Query(
|
||||
ctx context.Context, serverHost string, clientIP net.IP, question string, qType dns.Type,
|
||||
) (*model.Response, error) {
|
||||
msg := util.NewMsgWithQuestion(question, qType)
|
||||
clientID := extractClientIDFromHost(serverHost)
|
||||
|
||||
ctx, req := newRequest(ctx, clientIP, clientID, model.RequestProtocolTCP, msg)
|
||||
|
||||
return s.resolve(ctx, req)
|
||||
}
|
||||
|
||||
func createHTTPSRouter(cfg *config.Config) *chi.Mux {
|
||||
|
@ -249,7 +226,9 @@ func configureStaticAssetsHandler(router *chi.Mux) {
|
|||
func configureRootHandler(cfg *config.Config, router *chi.Mux) {
|
||||
router.Get("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.Header().Set(contentTypeHeader, htmlContentType)
|
||||
|
||||
t := template.New("index")
|
||||
|
||||
_, _ = t.Parse(web.IndexTmpl)
|
||||
|
||||
type HandlerLink struct {
|
||||
|
@ -262,11 +241,13 @@ func configureRootHandler(cfg *config.Config, router *chi.Mux) {
|
|||
Version string
|
||||
BuildTime string
|
||||
}
|
||||
|
||||
pd := PageData{
|
||||
Links: nil,
|
||||
Version: util.Version,
|
||||
BuildTime: util.BuildTime,
|
||||
}
|
||||
|
||||
pd.Links = []HandlerLink{
|
||||
{
|
||||
URL: "/docs/openapi.yaml",
|
||||
|
|
|
@ -104,10 +104,8 @@ var _ = BeforeSuite(func() {
|
|||
CustomDNS: config.CustomDNS{
|
||||
CustomTTL: config.Duration(3600 * time.Second),
|
||||
Mapping: config.CustomDNSMapping{
|
||||
HostIPs: map[string][]net.IP{
|
||||
"custom.lan": {net.ParseIP("192.168.178.55")},
|
||||
"lan.home": {net.ParseIP("192.168.178.56")},
|
||||
},
|
||||
"custom.lan": {&dns.A{A: net.ParseIP("192.168.178.55")}},
|
||||
"lan.home": {&dns.A{A: net.ParseIP("192.168.178.56")}},
|
||||
},
|
||||
},
|
||||
Conditional: config.ConditionalUpstream{
|
||||
|
@ -119,7 +117,7 @@ var _ = BeforeSuite(func() {
|
|||
},
|
||||
},
|
||||
Blocking: config.Blocking{
|
||||
BlackLists: map[string][]config.BytesSource{
|
||||
Denylists: map[string][]config.BytesSource{
|
||||
"ads": config.NewBytesSources(
|
||||
doubleclickFile.Path,
|
||||
bildFile.Path,
|
||||
|
@ -127,13 +125,13 @@ var _ = BeforeSuite(func() {
|
|||
),
|
||||
"youtube": config.NewBytesSources(youtubeFile.Path),
|
||||
},
|
||||
WhiteLists: map[string][]config.BytesSource{
|
||||
Allowlists: map[string][]config.BytesSource{
|
||||
"ads": config.NewBytesSources(heiseFile.Path),
|
||||
"whitelist": config.NewBytesSources(heiseFile.Path),
|
||||
"allowlist": config.NewBytesSources(heiseFile.Path),
|
||||
},
|
||||
ClientGroupsBlock: map[string][]string{
|
||||
"default": {"ads"},
|
||||
"clWhitelistOnly": {"whitelist"},
|
||||
"clAllowlistOnly": {"allowlist"},
|
||||
"clAdsAndYoutube": {"ads", "youtube"},
|
||||
"clYoutubeOnly": {"youtube"},
|
||||
},
|
||||
|
@ -265,7 +263,7 @@ var _ = Describe("Running DNS server", func() {
|
|||
})
|
||||
})
|
||||
Context("no blocking default group with sub domain", func() {
|
||||
It("Query with should not be blocked, sub domain is not in blacklist", func() {
|
||||
It("Query with should not be blocked, sub domain is not in denylist", func() {
|
||||
Expect(requestServer(util.NewMsgWithQuestion("bild.de.", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -274,8 +272,8 @@ var _ = Describe("Running DNS server", func() {
|
|||
))
|
||||
})
|
||||
})
|
||||
Context("domain is on white and blacklist default group", func() {
|
||||
It("Query with should not be blocked, domain is on white and blacklist", func() {
|
||||
Context("domain is on allow/denylist default group", func() {
|
||||
It("Query with should not be blocked, domain is on allow/denylist", func() {
|
||||
Expect(requestServer(util.NewMsgWithQuestion("heise.de.", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -284,9 +282,9 @@ var _ = Describe("Running DNS server", func() {
|
|||
))
|
||||
})
|
||||
})
|
||||
Context("domain is on client specific white list", func() {
|
||||
It("Query with should not be blocked, domain is on client's white list", func() {
|
||||
mockClientName.Store("clWhitelistOnly")
|
||||
Context("domain is on client specific allowlist", func() {
|
||||
It("Query with should not be blocked, domain is on client's allowlist", func() {
|
||||
mockClientName.Store("clAllowlistOnly")
|
||||
Expect(requestServer(util.NewMsgWithQuestion("heise.de.", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -295,9 +293,9 @@ var _ = Describe("Running DNS server", func() {
|
|||
))
|
||||
})
|
||||
})
|
||||
Context("block client whitelist only", func() {
|
||||
It("Query with should be blocked, client has only whitelist, domain is not on client's white list", func() {
|
||||
mockClientName.Store("clWhitelistOnly")
|
||||
Context("block client allowlist only", func() {
|
||||
It("Query with should be blocked, client has only allowlist, domain is not on client's allowlist", func() {
|
||||
mockClientName.Store("clAllowlistOnly")
|
||||
Expect(requestServer(util.NewMsgWithQuestion("google.de.", A))).
|
||||
Should(
|
||||
SatisfyAll(
|
||||
|
@ -531,8 +529,8 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(resp).Should(HaveHTTPStatus(http.StatusUnsupportedMediaType))
|
||||
})
|
||||
})
|
||||
When("Internal error occurs", func() {
|
||||
It("should return 'Internal server error'", func() {
|
||||
When("DNS error occurs", func() {
|
||||
It("should return 'ServFail'", func() {
|
||||
msg = util.NewMsgWithQuestion("error.", A)
|
||||
rawDNSMessage, err := msg.Pack()
|
||||
Expect(err).Should(Succeed())
|
||||
|
@ -542,7 +540,41 @@ var _ = Describe("Running DNS server", func() {
|
|||
Expect(err).Should(Succeed())
|
||||
DeferCleanup(resp.Body.Close)
|
||||
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusInternalServerError))
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
msg := new(dns.Msg)
|
||||
Expect(msg.Unpack(body)).Should(Succeed())
|
||||
Expect(msg.Rcode).Should(Equal(dns.RcodeServerFailure))
|
||||
})
|
||||
})
|
||||
When("Internal error occurs", func() {
|
||||
BeforeEach(func() {
|
||||
bak := sut.queryResolver
|
||||
sut.queryResolver = nil // trigger a panic
|
||||
DeferCleanup(func() { sut.queryResolver = bak })
|
||||
})
|
||||
|
||||
It("should return 'ServFail'", func() {
|
||||
msg = util.NewMsgWithQuestion("error.", A)
|
||||
rawDNSMessage, err := msg.Pack()
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
resp, err = http.Post(queryURL,
|
||||
"application/dns-message", bytes.NewReader(rawDNSMessage))
|
||||
Expect(err).Should(Succeed())
|
||||
DeferCleanup(resp.Body.Close)
|
||||
|
||||
Expect(resp).Should(HaveHTTPStatus(http.StatusOK))
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
msg := new(dns.Msg)
|
||||
Expect(msg.Unpack(body)).Should(Succeed())
|
||||
Expect(msg.Rcode).Should(Equal(dns.RcodeServerFailure))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -596,10 +628,8 @@ var _ = Describe("Running DNS server", func() {
|
|||
},
|
||||
CustomDNS: config.CustomDNS{
|
||||
Mapping: config.CustomDNSMapping{
|
||||
HostIPs: map[string][]net.IP{
|
||||
"custom.lan": {net.ParseIP("192.168.178.55")},
|
||||
"lan.home": {net.ParseIP("192.168.178.56")},
|
||||
},
|
||||
"custom.lan": {&dns.A{A: net.ParseIP("192.168.178.55")}},
|
||||
"lan.home": {&dns.A{A: net.ParseIP("192.168.178.56")}},
|
||||
},
|
||||
},
|
||||
Blocking: config.Blocking{BlockType: "zeroIp"},
|
||||
|
@ -642,10 +672,8 @@ var _ = Describe("Running DNS server", func() {
|
|||
},
|
||||
CustomDNS: config.CustomDNS{
|
||||
Mapping: config.CustomDNSMapping{
|
||||
HostIPs: map[string][]net.IP{
|
||||
"custom.lan": {net.ParseIP("192.168.178.55")},
|
||||
"lan.home": {net.ParseIP("192.168.178.56")},
|
||||
},
|
||||
"custom.lan": {&dns.A{A: net.ParseIP("192.168.178.55")}},
|
||||
"lan.home": {&dns.A{A: net.ParseIP("192.168.178.56")}},
|
||||
},
|
||||
},
|
||||
Blocking: config.Blocking{BlockType: "zeroIp"},
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue