Compare commits

...

116 Commits
v0.23 ... main

Author SHA1 Message Date
dependabot[bot] aef4eca7e0
build(deps): bump github.com/docker/docker (#1488)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.1.0+incompatible to 26.1.3+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.1.0...v26.1.3)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 21:58:57 +02:00
dependabot[bot] 5aa3103671
build(deps): bump github.com/onsi/ginkgo/v2 from 2.18.0 to 2.19.0 (#1498)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.18.0 to 2.19.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.18.0...v2.19.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-27 21:57:12 +02:00
Dimitri Herzog 459bda761a
chore: update testcontainers with modules (#1493) 2024-05-27 17:37:51 +02:00
Dimitri Herzog 3dcd310a9a
feat: CLI command for configuration validation (#1497) 2024-05-26 20:45:30 +02:00
Dimitri Herzog 5a5ba550d5
fix: configuration error if customDNS.mappings contains multiple entries with whitespace (#1496) 2024-05-26 19:49:50 +02:00
Dimitri Herzog daf3b029c8
chore: update golangci-lint (#1495) 2024-05-26 17:54:22 +02:00
Dimitri Herzog 1a4118b45d
chore: update golang version (#1494) 2024-05-26 17:52:45 +02:00
dependabot[bot] 4805cb60d2
build(deps): bump github.com/prometheus/client_golang (#1480)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.19.0 to 1.19.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.19.0...v1.19.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-26 17:52:16 +02:00
dependabot[bot] 3ab04562fe
--- (#1490)
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-24 10:02:22 +02:00
dependabot[bot] d51d39929f
build(deps): bump github.com/onsi/gomega from 1.33.0 to 1.33.1 (#1467)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.33.0 to 1.33.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.33.0...v1.33.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 11:59:27 +02:00
dependabot[bot] 2476d38529
build(deps): bump github.com/onsi/ginkgo/v2 from 2.17.2 to 2.17.3 (#1475)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.17.2 to 2.17.3.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.2...v2.17.3)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 11:01:18 +02:00
dependabot[bot] 63468a7168
build(deps): bump golang.org/x/tools from 0.20.0 to 0.21.0 (#1473)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.20.0 to 0.21.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 05:28:34 +00:00
dependabot[bot] 3482e93d4a
build(deps): bump golang.org/x/net from 0.24.0 to 0.25.0 (#1472)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/net/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 07:27:11 +02:00
dependabot[bot] bbdb80a8ad
build(deps): bump github.com/onsi/ginkgo/v2 from 2.17.1 to 2.17.2 (#1465)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.17.1 to 2.17.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.1...v2.17.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 07:26:52 +02:00
dependabot[bot] d8efa79496
build(deps): bump gorm.io/gorm from 1.25.9 to 1.25.10 (#1464)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.9 to 1.25.10.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.9...v1.25.10)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 07:26:33 +02:00
dependabot[bot] 4ebe1ef21a
build(deps): bump github.com/miekg/dns from 1.1.58 to 1.1.59 (#1452)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.58 to 1.1.59.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.58...v1.1.59)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:49:13 +02:00
dependabot[bot] 7f20d17d2e
build(deps): bump github.com/onsi/gomega from 1.32.0 to 1.33.0 (#1455)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:48:40 +02:00
dependabot[bot] cbbe8d46f0
build(deps): bump github.com/avast/retry-go/v4 from 4.5.1 to 4.6.0 (#1456)
Bumps [github.com/avast/retry-go/v4](https://github.com/avast/retry-go) from 4.5.1 to 4.6.0.
- [Release notes](https://github.com/avast/retry-go/releases)
- [Commits](https://github.com/avast/retry-go/compare/4.5.1...4.6.0)

---
updated-dependencies:
- dependency-name: github.com/avast/retry-go/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:48:16 +02:00
dependabot[bot] 62b1354fba
build(deps): bump github.com/docker/docker (#1459)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.1+incompatible to 26.1.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.1...v26.1.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:47:46 +02:00
Thomas Anderson e99c98b4c2
feat: log the rule which is the cause of blocking (#1460)
Co-authored-by: ThinkChaos <ThinkChaos@users.noreply.github.com>
2024-04-24 12:58:29 -04:00
dependabot[bot] 58c5069803
build(deps): bump github.com/docker/docker (#1444)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.0+incompatible to 26.0.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.0...v26.0.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-16 12:49:33 +02:00
Kwitsch debac9eaa8
Refactoring/linter (#1447)
* update golangci-lint to v1.57.2

* linter fixes
2024-04-12 16:44:50 -04:00
Kwitsch 30cda6c367
Feature: binary build workflow (#1445)
* added build binary workflow

* error message rework
2024-04-12 16:42:17 -04:00
ThinkChaos dbd1390589 tests(resolver): fix data race introduced by querylog ignore
Race only happens in tests: the write removed here can happen at the
same time as `writeLog` is reading the struct field since cancelling
the context doesn't guarantee immediate shutdown.
We can just use the existing channel so the field becomes read-only,
avoiding the race.

Example CI failure:
https://github.com/0xERR0R/blocky/actions/runs/8632315589/job/23662702020

For local repro, adding a 2ms sleep to `writeLog`'s startup was enough
for me.
2024-04-11 18:34:30 -04:00
ThinkChaos ef8c008249 docs: use relative links for Grafana dashboards
Ensure the linked file matches the current blocky version, not what's
in main, which is no longer the same since switch to single branch dev.
2024-04-11 18:33:09 -04:00
ThinkChaos 90b9677198 ci: fix docs workflow not running for branches
Seems like if you put `tags:`, then you must also put `branches:` even
if you put `paths:`
2024-04-11 18:33:09 -04:00
ThinkChaos bcd1381e18 feat: update list config and code to use "allow/deny" language 2024-04-11 18:33:09 -04:00
dependabot[bot] 3515483795
build(deps): bump gorm.io/gorm from 1.25.8 to 1.25.9 (#1418)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.8 to 1.25.9.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.8...v1.25.9)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 10:21:52 +02:00
ThinkChaos d5b6ee93b5 fix: use proxy env vars via Go default HTTP Transport values
Don't build `http.Transport` instances from scratch, but start from
`http.DefaultTransport` and override what is needed.
2024-04-10 09:43:57 -04:00
dependabot[bot] 5040ed8216
build(deps): bump github.com/testcontainers/testcontainers-go/modules/mariadb (#1438)
Bumps [github.com/testcontainers/testcontainers-go/modules/mariadb](https://github.com/testcontainers/testcontainers-go) from 0.29.1 to 0.30.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.29.1...v0.30.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/mariadb
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 10:49:18 +00:00
dependabot[bot] 1d71bc525a
build(deps): bump github.com/testcontainers/testcontainers-go/modules/postgres (#1439)
Bumps [github.com/testcontainers/testcontainers-go/modules/postgres](https://github.com/testcontainers/testcontainers-go) from 0.29.1 to 0.30.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.29.1...v0.30.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/postgres
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 10:48:25 +00:00
dependabot[bot] 166ecbeb40
build(deps): bump github.com/testcontainers/testcontainers-go/modules/redis (#1437)
Bumps [github.com/testcontainers/testcontainers-go/modules/redis](https://github.com/testcontainers/testcontainers-go) from 0.29.1 to 0.30.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.29.1...v0.30.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/redis
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 12:47:19 +02:00
dependabot[bot] f61c93b185
build(deps): bump golang.org/x/tools from 0.19.0 to 0.20.0 (#1431)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.19.0 to 0.20.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-10 08:10:04 +02:00
ThinkChaos 9d65b9395d feat: add `queryLog.ignore.sudn` option to ignore SUDN responses 2024-04-05 15:32:09 -04:00
ThinkChaos c56f0f91ca ci(fork-sync): add hint that it can be disabled 2024-04-02 16:53:35 -04:00
ThinkChaos 75c2a6f9f6 ci(makefile): use `push: paths:` to limit job runs 2024-04-02 16:53:35 -04:00
ThinkChaos 1a035c3559 ci(docs): use `push: paths:` to limit job runs 2024-04-02 16:53:35 -04:00
ThinkChaos b5682980f7 ci: allow concurrent "Makefile" workflows
I like to push my commits one by one so they all get
the full CI validation.
This limits forces me to push a commit, and wait for the result before
pushing another.
2024-04-02 16:53:35 -04:00
ThinkChaos 1edf8cc355 fix: obfuscate secrets using a constant length string 2024-04-02 15:03:40 -04:00
ThinkChaos 2c6b704433 fix(log): don't print querylog target password when using a database 2024-04-02 15:03:40 -04:00
dependabot[bot] 28f979fdf7
build(deps): bump github.com/onsi/ginkgo/v2 from 2.17.0 to 2.17.1 (#1415)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.17.0 to 2.17.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.17.0...v2.17.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-25 12:00:50 +01:00
dependabot[bot] 80e7b14aad
build(deps): bump github.com/docker/docker (#1412)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 25.0.4+incompatible to 26.0.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v25.0.4...v26.0.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-22 12:44:02 +01:00
dependabot[bot] 2d49a9f455
build(deps): bump gorm.io/driver/mysql from 1.5.5 to 1.5.6 (#1413)
Bumps [gorm.io/driver/mysql](https://github.com/go-gorm/mysql) from 1.5.5 to 1.5.6.
- [Commits](https://github.com/go-gorm/mysql/compare/v1.5.5...v1.5.6)

---
updated-dependencies:
- dependency-name: gorm.io/driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-22 12:43:21 +01:00
ThinkChaos 73e5d6ab88 refactor: remove `model.Request.Log` in favor of use `Context` 2024-03-19 19:10:07 -04:00
ThinkChaos 0a47eaad09 feat: add a unique ID (`req_id`) to all logs related to a request 2024-03-19 19:10:07 -04:00
ThinkChaos 4919ffac0d fix(server): use RCode=ServFail instead of HTTP 500 for internal errors
RFC 8484 Section 4.2.1:
> A successful HTTP response with a 2xx status code (see
> Section 6.3 of RFC7231) is used for any valid DNS response,
> regardless of the DNS response code.  For example, a successful 2xx
> HTTP status code is used even with a DNS message whose DNS response
> code indicates failure, such as SERVFAIL or NXDOMAIN.
https://www.rfc-editor.org/rfc/rfc8484#section-4.2.1
2024-03-19 19:10:07 -04:00
ThinkChaos 3fcf379df7 refactor(util): make `LogOnError` get the log from a `Context` 2024-03-19 19:10:07 -04:00
ThinkChaos b335887992 refactor(log): store log in context so it's automatically propagated 2024-03-19 19:10:07 -04:00
ThinkChaos d83b7432d4 refactor(log): use `logrus.Level` directly and document `trace` level 2024-03-19 19:10:07 -04:00
dependabot[bot] 9d50941e2f
build(deps): bump gorm.io/gorm from 1.25.7 to 1.25.8 (#1405)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.7 to 1.25.8.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.7...v1.25.8)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-19 09:28:56 +01:00
dependabot[bot] 12e5ffa6c0
build(deps): bump github.com/onsi/gomega from 1.31.1 to 1.32.0 (#1406)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.31.1 to 1.32.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.31.1...v1.32.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-19 08:27:16 +00:00
dependabot[bot] 24f1187f3d
build(deps): bump github.com/onsi/ginkgo/v2 from 2.16.0 to 2.17.0 (#1407)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.16.0 to 2.17.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.16.0...v2.17.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-19 09:25:54 +01:00
ThinkChaos f5bd69cf12 docs(installation): style and minor content tweaks 2024-03-18 12:44:56 -04:00
ThinkChaos 5242fb68ad docs(installation): move "frontend" and add "lists updater" to projects 2024-03-18 12:44:56 -04:00
ThinkChaos 2ecdfd8d78 docs(installation): remove Kubernetes and cleanup list 2024-03-18 12:44:56 -04:00
dependabot[bot] f00dbb421a
build(deps): bump gorm.io/driver/mysql from 1.5.4 to 1.5.5 (#1404)
Bumps [gorm.io/driver/mysql](https://github.com/go-gorm/mysql) from 1.5.4 to 1.5.5.
- [Commits](https://github.com/go-gorm/mysql/compare/v1.5.4...v1.5.5)

---
updated-dependencies:
- dependency-name: gorm.io/driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-18 13:02:40 +01:00
Kwitsch c3a319f199
refactoring - e2e network (#1401)
* change to testcontainers-go/network
2024-03-18 13:02:03 +01:00
Kwitsch 7eef4bf6e2
Build Cache Optimization (#1402)
* don't copy if we mount the files anyway

* use newer alpine version

* cache apk pkgs

* changeing workdir is not needed in ziggoimg

* cache some more

* preload go modules
2024-03-15 17:22:26 -04:00
dependabot[bot] ca7497897e
build(deps): bump github.com/alicebob/miniredis/v2 from 2.31.1 to 2.32.1 (#1400)
Bumps [github.com/alicebob/miniredis/v2](https://github.com/alicebob/miniredis) from 2.31.1 to 2.32.1.
- [Release notes](https://github.com/alicebob/miniredis/releases)
- [Changelog](https://github.com/alicebob/miniredis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/alicebob/miniredis/compare/v2.31.1...v2.32.1)

---
updated-dependencies:
- dependency-name: github.com/alicebob/miniredis/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-14 10:01:10 +01:00
dependabot[bot] 09ce2a148a
build(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#1399)
Bumps google.golang.org/protobuf from 1.32.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-14 10:00:40 +01:00
dependabot[bot] 160e159113
build(deps): bump github.com/testcontainers/testcontainers-go/modules/redis (#1395)
Bumps [github.com/testcontainers/testcontainers-go/modules/redis](https://github.com/testcontainers/testcontainers-go) from 0.28.0 to 0.29.1.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.28.0...v0.29.1)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/redis
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 11:27:10 +00:00
dependabot[bot] fc490ec61c
build(deps): bump github.com/testcontainers/testcontainers-go/modules/postgres (#1394)
Bumps [github.com/testcontainers/testcontainers-go/modules/postgres](https://github.com/testcontainers/testcontainers-go) from 0.28.0 to 0.29.1.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.28.0...v0.29.1)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/postgres
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-12 12:25:19 +01:00
dependabot[bot] c3c229125d
build(deps): bump github.com/docker/docker (#1396)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 25.0.3+incompatible to 25.0.4+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v25.0.3...v25.0.4)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 22:19:55 +01:00
dependabot[bot] e5cb34ebc7
build(deps): bump gorm.io/driver/postgres from 1.5.6 to 1.5.7 (#1397)
Bumps [gorm.io/driver/postgres](https://github.com/go-gorm/postgres) from 1.5.6 to 1.5.7.
- [Commits](https://github.com/go-gorm/postgres/compare/v1.5.6...v1.5.7)

---
updated-dependencies:
- dependency-name: gorm.io/driver/postgres
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 22:19:34 +01:00
dependabot[bot] 615fd81bba
build(deps): bump github.com/testcontainers/testcontainers-go/modules/mariadb (#1393)
Bumps [github.com/testcontainers/testcontainers-go/modules/mariadb](https://github.com/testcontainers/testcontainers-go) from 0.28.0 to 0.29.1.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.28.0...v0.29.1)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/mariadb
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 20:52:20 +00:00
dependabot[bot] 6bcc1d0606
build(deps): bump github.com/testcontainers/testcontainers-go (#1392)
Bumps [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) from 0.28.0 to 0.29.1.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.28.0...v0.29.1)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 20:50:59 +00:00
dependabot[bot] ddbf7a3965
build(deps): bump github.com/stretchr/testify from 1.8.4 to 1.9.0 (#1386)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-07 21:50:07 +01:00
dependabot[bot] 4d8595f8ec
build(deps): bump golang.org/x/tools from 0.18.0 to 0.19.0 (#1387)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.18.0 to 0.19.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.18.0...v0.19.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-06 14:57:16 +00:00
dependabot[bot] d32f3b83d2
build(deps): bump github.com/onsi/ginkgo/v2 from 2.15.0 to 2.16.0 (#1388)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.15.0 to 2.16.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.15.0...v2.16.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-06 15:56:39 +01:00
dependabot[bot] 82578d2ee5
build(deps): bump golang.org/x/net from 0.21.0 to 0.22.0 (#1389)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.22.0.
- [Commits](https://github.com/golang/net/compare/v0.21.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-06 15:56:15 +01:00
dependabot[bot] f93d3f834f
build(deps): bump github.com/prometheus/client_golang (#1384)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.18.0 to 1.19.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.19.0/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.18.0...v1.19.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-01 16:47:30 +01:00
dependabot[bot] efc14d25ca
build(deps): bump github.com/go-chi/chi/v5 from 5.0.11 to 5.0.12 (#1381)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.11 to 5.0.12.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.11...v5.0.12)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-19 21:23:44 +01:00
dependabot[bot] 07b864e7e7
build(deps): bump github.com/testcontainers/testcontainers-go/modules/postgres (#1379)
Bumps [github.com/testcontainers/testcontainers-go/modules/postgres](https://github.com/testcontainers/testcontainers-go) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/postgres
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 14:59:24 +00:00
dependabot[bot] 7ce7f9ad50
build(deps): bump github.com/testcontainers/testcontainers-go/modules/mariadb (#1380)
Bumps [github.com/testcontainers/testcontainers-go/modules/mariadb](https://github.com/testcontainers/testcontainers-go) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/mariadb
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 14:57:53 +00:00
dependabot[bot] ed2072071f
build(deps): bump github.com/testcontainers/testcontainers-go/modules/redis (#1378)
Bumps [github.com/testcontainers/testcontainers-go/modules/redis](https://github.com/testcontainers/testcontainers-go) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go/modules/redis
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 15:57:02 +01:00
dependabot[bot] 85ae0e70e5
build(deps): bump github.com/testcontainers/testcontainers-go (#1377)
Bumps [github.com/testcontainers/testcontainers-go](https://github.com/testcontainers/testcontainers-go) from 0.27.0 to 0.28.0.
- [Release notes](https://github.com/testcontainers/testcontainers-go/releases)
- [Commits](https://github.com/testcontainers/testcontainers-go/compare/v0.27.0...v0.28.0)

---
updated-dependencies:
- dependency-name: github.com/testcontainers/testcontainers-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-15 15:56:47 +01:00
Dimitri Herzog 716ad9498f
chore(build): update testcontainers dependency (#1376) 2024-02-14 08:20:18 +01:00
dependabot[bot] e98e3432c5
build(deps): bump golang.org/x/tools from 0.17.0 to 0.18.0 (#1375)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.17.0 to 0.18.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.17.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 10:05:56 +00:00
dependabot[bot] db016bbdaa
build(deps): bump golang.org/x/net from 0.20.0 to 0.21.0 (#1374)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/net/compare/v0.20.0...v0.21.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 11:05:12 +01:00
dependabot[bot] 29cd78071f
build(deps): bump gorm.io/driver/postgres from 1.5.4 to 1.5.6 (#1370)
Bumps [gorm.io/driver/postgres](https://github.com/go-gorm/postgres) from 1.5.4 to 1.5.6.
- [Commits](https://github.com/go-gorm/postgres/compare/v1.5.4...v1.5.6)

---
updated-dependencies:
- dependency-name: gorm.io/driver/postgres
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 14:57:32 +01:00
dependabot[bot] 7de0dfe111
build(deps): bump gorm.io/gorm from 1.25.6 to 1.25.7 (#1369)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.6 to 1.25.7.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.6...v1.25.7)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 14:57:24 +01:00
dependabot[bot] b7abcc303b
build(deps): bump gorm.io/driver/mysql from 1.5.2 to 1.5.4 (#1371)
Bumps [gorm.io/driver/mysql](https://github.com/go-gorm/mysql) from 1.5.2 to 1.5.4.
- [Commits](https://github.com/go-gorm/mysql/compare/v1.5.2...v1.5.4)

---
updated-dependencies:
- dependency-name: gorm.io/driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 13:37:56 +00:00
dependabot[bot] 10e293fdb1
build(deps): bump gorm.io/driver/sqlite from 1.5.4 to 1.5.5 (#1367)
Bumps [gorm.io/driver/sqlite](https://github.com/go-gorm/sqlite) from 1.5.4 to 1.5.5.
- [Commits](https://github.com/go-gorm/sqlite/compare/v1.5.4...v1.5.5)

---
updated-dependencies:
- dependency-name: gorm.io/driver/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 14:36:47 +01:00
dependabot[bot] 8bb5b177af
build(deps): bump codecov/codecov-action from 3 to 4 (#1363)
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 14:36:28 +01:00
dependabot[bot] e26ebfc406
build(deps): bump github.com/docker/docker (#1372)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 25.0.2+incompatible to 25.0.3+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v25.0.2...v25.0.3)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 14:36:08 +01:00
Ben fe84ab8ca2
fix: api regression breaking `blocky blocking disable` (#1373)
* fix: Corrects ability to disable all blocking from the CLI by not providing any groups

* fix: More appropriately correct the issue preventing disabled blocking directly when parsing parameters

* Use the same length logic previously seen

* Code reviewcomments

* Remove redundant check

* Revert nil check removal; Removing this broke tests
2024-02-10 13:06:01 -05:00
Ben 9f633f18d0
feat: Support defining records by dns zone format (#1360)
* feat: Support zonefile configuration for custom dns mapping

* docs: Update configuration.md

* Rename var to ok

* Linter fixes

* Remove hashes in test describe description

* Implement PR comments; zoneFileMapping -> zone, initialize with proper sizes

* Remove custom CNAME parsing

* Utilize TTL defined in zone file

* Link to wikipedia's example file

* Test to confirm that a relative zone entry without an $ORIGIN returns an error

* Write a test covering the $INCLUDE directive

* Write a test confirming that a dns zone can result in more than 1 RR

* Linting

* fix: Use proper matchers in CustomDNS Zone tests; Update configuration.md description

* Pull in config directory to support relative $INCLUDE

* Added tests to ensure the ability to use both bare filenames as well as relative filenames when using the $INCLUDE directive

* Shorten test description (Linting error)

* Move Assignment of z.RRs to the end of the UnmarshallYAML function

* Moved tests for relative $INCLUDE zones to config_test. Added test case when config param passed to blocky is a directory

* Corrected test case to _actually_ test againt bare file names
2024-02-09 17:28:58 +01:00
Ben McHone 178dbb740e fix: Parse time from file names in the local system timezone 2024-02-06 09:46:43 -05:00
dependabot[bot] 2973045632
build(deps): bump github.com/docker/docker (#1362)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 25.0.1+incompatible to 25.0.2+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v25.0.1...v25.0.2)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 07:53:12 +01:00
Rahil Bhimjiani 92b93893c7 docs: add links for Gentoo package
Signed-off-by: Rahil Bhimjiani <me@rahil.rocks>
2024-01-30 10:12:04 -05:00
dependabot[bot] cf5c09a3d0
build(deps): bump github.com/onsi/gomega from 1.31.0 to 1.31.1 (#1345)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.31.0 to 1.31.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.31.0...v1.31.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-30 08:26:14 +01:00
Ben b8b4dc323a
feat: support CNAME records in customDNS mappings (#1352)
Co-authored-by: Ben McHone <ben@mchone.dev>
2024-01-29 11:22:03 -05:00
dependabot[bot] 3817d98e74
build(deps): bump github.com/google/uuid from 1.5.0 to 1.6.0 (#1349)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.5.0 to 1.6.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:43:39 +01:00
dependabot[bot] 5d0397d571
build(deps): bump github.com/miekg/dns from 1.1.57 to 1.1.58 (#1341)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.57 to 1.1.58.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.57...v1.1.58)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:43:21 +01:00
dependabot[bot] ac2bfd90ae
build(deps): bump github.com/docker/docker (#1350)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.7+incompatible to 25.0.1+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v24.0.7...v25.0.1)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:33:39 +01:00
Shizun Ge d2cb593d32
fix(grafana): move panel around. keep related panels in the same row. (#1344)
When on mobile phone, there is only one column, and the panels display row by row.
2024-01-29 12:33:15 +01:00
dependabot[bot] 3eaee7a84e
build(deps): bump gorm.io/gorm from 1.25.5 to 1.25.6 (#1358)
Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.25.5 to 1.25.6.
- [Release notes](https://github.com/go-gorm/gorm/releases)
- [Commits](https://github.com/go-gorm/gorm/compare/v1.25.5...v1.25.6)

---
updated-dependencies:
- dependency-name: gorm.io/gorm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-29 12:30:42 +01:00
ThinkChaos ad1ef0bcfb fix: don't use upstream `ServFail` responses besides forwarding them
Don't consider `ServFail` as a TCP/UDP race win.
Use an error to also make sure we don't, for instance, cache such
responses.
2024-01-27 17:25:33 -05:00
ThinkChaos dd76cf5bb0 fix: add `Resolver.String` so logs don't use Go's default format 2024-01-18 10:47:04 -05:00
ThinkChaos f0ad412d8d refactor(server): add `resolve` for common query code
Ensure all queries go through that common code path so we always enable
compression, truncate if required, etc.
2024-01-18 10:46:54 -05:00
ThinkChaos e9a1e8974d feat(api): support client name lookup when querying via the API 2024-01-18 10:46:54 -05:00
dependabot[bot] aaee562460
build(deps): bump github.com/onsi/gomega from 1.30.0 to 1.31.0 (#1340)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.30.0 to 1.31.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.30.0...v1.31.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-18 15:18:40 +01:00
dependabot[bot] 879087607b
build(deps): bump github.com/onsi/ginkgo/v2 from 2.14.0 to 2.15.0 (#1339)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.14.0 to 2.15.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.14.0...v2.15.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-18 11:03:20 +01:00
dependabot[bot] c8b1dd67a6
build(deps): bump github.com/alicebob/miniredis/v2 from 2.31.0 to 2.31.1 (#1324)
Bumps [github.com/alicebob/miniredis/v2](https://github.com/alicebob/miniredis) from 2.31.0 to 2.31.1.
- [Release notes](https://github.com/alicebob/miniredis/releases)
- [Changelog](https://github.com/alicebob/miniredis/blob/master/CHANGELOG.md)
- [Commits](https://github.com/alicebob/miniredis/compare/v2.31.0...v2.31.1)

---
updated-dependencies:
- dependency-name: github.com/alicebob/miniredis/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-18 10:40:05 +01:00
Kwitsch 2d3ad83087
Refactoring/e2e tests (#1316)
* WithNetwork refactoring

* removed tmpDir for blocky

* removed tmpDir from HTTPServer
2024-01-17 17:16:16 +01:00
dependabot[bot] 49c808f71d
build(deps): bump github.com/onsi/ginkgo/v2 from 2.13.2 to 2.14.0 (#1334)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.2 to 2.14.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.2...v2.14.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 17:05:26 +01:00
dependabot[bot] e686a1d39c
build(deps): bump golang.org/x/tools from 0.16.1 to 0.17.0 (#1335)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.16.1 to 0.17.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.16.1...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 17:04:54 +01:00
dependabot[bot] 75a7914ec0
build(deps): bump golang.org/x/net from 0.19.0 to 0.20.0 (#1330)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/net/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 17:04:23 +01:00
Shizun Ge 95bd01360b
feat: do not hardcode the job name in the grafana dashboard.(#1326) 2024-01-17 17:01:21 +01:00
ThinkChaos 7abbaefb07 chore(docs): update material emoji config to non deprecated options 2024-01-12 09:35:41 -05:00
ThinkChaos 79fc06f6c2 test(config): make sure `docs/config.yml` doesn't use deprecated options 2024-01-12 09:35:41 -05:00
DerRockWolf 999a16847f
Fix docs: upstream spelling (#1333)
* Fix docs upstream spelling

* Update configuration.md
2024-01-10 10:06:33 -05:00
dependabot[bot] b302582e40
build(deps): bump github.com/DATA-DOG/go-sqlmock from 1.5.1 to 1.5.2 (#1327)
Bumps [github.com/DATA-DOG/go-sqlmock](https://github.com/DATA-DOG/go-sqlmock) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/DATA-DOG/go-sqlmock/releases)
- [Commits](https://github.com/DATA-DOG/go-sqlmock/compare/v1.5.1...v1.5.2)

---
updated-dependencies:
- dependency-name: github.com/DATA-DOG/go-sqlmock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 14:12:24 +01:00
dependabot[bot] e12f6b54da
build(deps): bump github.com/docker/go-connections from 0.4.0 to 0.5.0 (#1328)
Bumps [github.com/docker/go-connections](https://github.com/docker/go-connections) from 0.4.0 to 0.5.0.
- [Commits](https://github.com/docker/go-connections/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: github.com/docker/go-connections
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-08 07:42:13 +01:00
dependabot[bot] 5cde62f354
build(deps): bump github.com/prometheus/client_golang (#1322)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.18.0/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.17.0...v1.18.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-06 22:52:21 +01:00
dependabot[bot] 1d1206f1ea
build(deps): bump github.com/oapi-codegen/runtime from 1.1.0 to 1.1.1 (#1325)
Bumps [github.com/oapi-codegen/runtime](https://github.com/oapi-codegen/runtime) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/oapi-codegen/runtime/releases)
- [Commits](https://github.com/oapi-codegen/runtime/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: github.com/oapi-codegen/runtime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-05 22:56:28 +01:00
105 changed files with 3014 additions and 1313 deletions

113
.github/workflows/build-bin.yml vendored Normal file
View File

@ -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

View File

@ -2,11 +2,16 @@ name: docs
on:
push:
branches:
- "**"
tags:
- v*
branches:
- '**'
paths:
- .github/workflows/**
- mkdocs.yml
- docs/**
concurrency:
group: ${{ github.workflow }}

View File

@ -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

View File

@ -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

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ vendor/
coverage.html
coverage.txt
coverage/
blocky

View File

@ -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:

View File

@ -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 \

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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"

View File

@ -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

View File

@ -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 {

View File

@ -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",

View File

@ -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())

View File

@ -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, ...)")

View File

@ -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

View File

@ -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))
})

View File

@ -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:

View File

@ -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)

38
cmd/validate.go Normal file
View File

@ -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
}

52
cmd/validate_test.go Normal file
View File

@ -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())
})
})
})

View File

@ -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)
})
}

View File

@ -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))
})
})
})

View File

@ -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")

View File

@ -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",

View File

@ -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
}

View File

@ -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"))
})
})
})

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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() {

View File

@ -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))
}

View File

@ -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)))
})
})
})

View File

@ -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.

View File

@ -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)

View File

@ -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"
}
]
},

View File

@ -243,7 +243,7 @@
]
}
],
"title": "Blocked by Blacklist",
"title": "Blocked by Denylist",
"type": "piechart"
},
{

View File

@ -243,7 +243,7 @@
]
}
],
"title": "Blocked by Blacklist",
"title": "Blocked by Denylist",
"type": "piechart"
},
{

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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:",

View File

@ -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:",

View File

@ -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()
}

View File

@ -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)
})

114
e2e/helper.go Normal file
View File

@ -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)
}

View File

@ -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",
))
})
})

View File

@ -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:",

View File

@ -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:",

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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:

71
helpertest/http.go Normal file
View File

@ -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)
}

View File

@ -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}

View File

@ -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"))
})
})
})
})

View File

@ -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

View File

@ -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()

View File

@ -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.

View File

@ -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())
})
})

46
log/context.go Normal file
View File

@ -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)
})
}

View File

@ -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:

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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),
)
})

View File

@ -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
}

View File

@ -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},
},

View File

@ -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)

View File

@ -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")

View File

@ -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()
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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() {

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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()
}

View File

@ -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]++

View File

@ -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

View File

@ -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{

View File

@ -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)

View File

@ -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

View File

@ -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() {

View File

@ -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(),

View File

@ -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,

View File

@ -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,

View File

@ -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(

View File

@ -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

View File

@ -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",

View File

@ -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