208 Commits
Author SHA1 Message Date
SisyphusMD 3bba120904 feat(redis): recover optional connections after startup failure (#2260) 2026-10-07 03:03:22 +00:00
Dimitri Herzog 78d5367151 fix: don't pass EDNS0 DNS Cookies through (#2252) 2026-09-05 16:24:24 +00:00
JackandDimitri Herzog ff2aae4437 fix(server): always answer an EDNS0 query with an OPT record (#2240)
* fix(server): always answer an EDNS0 query with an OPT record

Cache hits are served from bytes packed without the OPT record, so a
client that sent one got none back. RFC 6891 §6.1.1 requires an OPT in
the response to any EDNS0 query. systemd-resolved reads its absence as a
server without EDNS0 support, downgrades, and re-probes every ~45 min;
lookups inside a probe window fail with SERVFAIL.

* fix(server): repair the zero payload size on a self-added OPT record

util.SetEdns0Option builds an OPT record with only Name and Rrtype set,
leaving the class field - the UDP payload size - at zero. The EDE resolver
uses it to annotate cache hits, blocked answers, custom DNS and hosts file
responses, so with ede.enable those responses already carry an OPT and take
the mirroring branch, where only the DO bit was set.

RFC 6891 6.2.4 makes a peer read a zero payload size as 512, so blocky
advertised a 512 byte receive buffer while its listener accepts 65535, and
the missing OPT this branch set out to add was never missing there in the
first place.

Add a DO=false spec for the synthesized OPT as well: the existing specs for
a cleared DO bit all use a chain that puts an OPT on the response, so they
exercise the mirroring branch and leave the new one uncovered.

Claude-Session: https://claude.ai/code/session_01JKQa9d3Uiwu5JfJwEfVC82

---------

Co-authored-by: Dimitri Herzog <[email protected]>
2026-09-05 13:46:39 +00:00
Dimitri Herzog db8d889b7b fix(server): compress responses larger than 512 bytes (#2213)
* fix(server): compress responses larger than 512 bytes

Since #2102 the server lets Truncate decide compression, and Truncate
clears Compress whenever the message already fits uncompressed. Fitting
the buffer the client advertised is not sufficient: that client may be a
forwarder relaying our answer to a stub resolver with a smaller buffer,
and it can only truncate what it received.

A query arriving via such a forwarder carries the forwarder's OPT record,
so getMaxResponseSize returns its buffer (1232 or more) rather than 512.
An eight-record A answer for a 45-byte name is 562 bytes uncompressed and
202 compressed: both fit 1232, so we now send the uncompressed one, and
the forwarder has to truncate it for its non-EDNS0 clients -- typically by
dropping every section, leaving them with TC and no answer at all. Before
#2102 the same answer went out compressed, fit 512, and was relayed intact.

Compress anything that would not fit a bare 512-byte UDP message so it
survives that hop. Below that threshold there is nothing to gain, so the
compression-map alloc #2102 set out to avoid is still skipped for the
short responses that make up the bulk of the traffic.

Fixes #2212

* test(server): guard that the compression regression test stays meaningful

Assert the answer really exceeds 512 bytes uncompressed, so shrinking the
RR set can't leave the test passing without exercising the scenario.
2026-08-11 05:18:07 +02:00
Kevin ParkandDimitri Herzog a42d656672 fix(dnssec): don't return DNSSEC records to clients with the DO bit clear (#2203)
* refactor(server): group the client-facing response normalization

The response the server writes back is normalized against the query as the
client sent it, not against request.Req, which the resolver chain mutates in
place. That was two captured locals and three statements spread across
resolve(); collect them in a clientQuery value captured before resolution and
applied afterwards, so further per-client normalization has one home.

No behavior change: the existing server specs pass unchanged.

* fix(dnssec): don't return DNSSEC records to clients with the DO bit clear

Fixes #2202.

A validating resolver must query upstream with the DO bit set, so the DNSSEC
resolver sets it on every request regardless of what the client asked for. The
records that come back were then forwarded verbatim, so a client that left DO
clear received the RRSIG (and NSEC/NSEC3/DNSKEY/DS) records blocky had fetched
for its own validation.

Normalize the response against the client's DO bit in clientQuery:

- strip the DNSSEC records, keeping any type the query explicitly asked for, so
  a DNSKEY or DS query is still answered, and keeping everything for an ANY
  query (RFC 4035 section 3.2.1, RFC 3225 section 3)
- copy the query's DO bit into the response's OPT record (RFC 3225 section 3)
- clear AD unless the client set DO or AD (RFC 6840 section 5.8)

Stripping runs before Truncate, so a signed answer that only exceeded the
client's buffer because of the signatures is no longer truncated.

This sits in the server rather than in the DNSSEC resolver because the cache is
keyed on qtype and name only: an entry populated by a DO client is served to
every other client of that name, so the records have to be stripped per client
even when dnssec.validate is off and the DNSSEC resolver is a no-op.

Not addressed: the reverse of that cache behaviour, where a DO=1 client is
served an unsigned entry populated by a DO=0 client (#1741). That needs a
cache-key change; with dnssec.validate enabled it cannot happen, because the
chain always fetches with DO set.

---------

Co-authored-by: Dimitri Herzog <[email protected]>
2026-08-04 20:20:05 +00:00
Dimitri Herzog 472bac923d fix(stats): attribute filtered and non-FQDN queries to the client name (#2167) 2026-07-18 20:18:20 +00:00
Murat M 0a206718a0 fix(cache): set DO bit on prefetch reloads to keep DNSSEC-signed entry valid (#2158) 2026-07-18 19:50:56 +00:00
Dimitri Herzog 4b524e868c fix(ecs): apply useAsClient above cache and client-name lookup (#2140) (#2141) 2026-06-24 20:51:39 +00:00
Dimitri Herzog 4018d9e5da feat: in-memory statistics subsystem with /api/stats REST endpoint (#2093) 2026-06-12 20:13:48 +00:00
Dimitri Herzog 0b70e5cfd2 feat(resolver): add DNS rebinding protection (#2111) 2026-06-12 16:02:04 +00:00
Dimitri Herzog 80f742e6f0 fix(server): answer browser CORS preflights for custom headers and Private Network Access (#2109) 2026-06-12 09:27:18 +00:00
Dimitri Herzog d5028066c9 perf(server): let Truncate decide response compression (#2102) 2026-06-11 05:32:44 +00:00
Dimitri Herzog bee2d8bb87 feat(resolver): UDP-first plain-DNS upstream and EDNS0 buffer floor (#2100) 2026-06-10 14:57:35 +02:00
Kostiantyn Astakhov 7abca446a5 feat: support PROXY protocol on proxied DoT/DoH listeners (#2094) 2026-06-10 08:57:51 +02:00
Dimitri Herzog 5d806b6a41 feat(server): opt-in IP_FREEBIND for DNS listeners (#2078) 2026-06-02 12:41:45 +02:00
Dimitri Herzog 5456628f8b chore: enable additional golangci-lint linters (#2073) 2026-05-23 22:33:36 +02:00
Dimitri Herzog fdcf351add feat: resolve client names from hosts file and custom DNS (#1964) (#2069)
Client name resolution previously only used the static clientLookup.clients
mapping or a reverse-DNS query against a configured clientLookup.upstream.
With neither set, the client IP was used as its own name even when Blocky
already knew the name from a hosts file or custom DNS entry.

ClientNamesResolver now consults Blocky's in-memory reverse sources
(custom DNS, hosts files) before the rDNS upstream, performing no network
round-trip. Resolution order: static mapping -> local sources -> rDNS
upstream -> IP fallback. This also makes the per-client CSV log filename
use the resolved hostname.
2026-05-22 11:50:58 +02:00
Dimitri Herzog bf19d6d5f7 feat: config JSON schema generation and schema-driven validation (#2066) 2026-05-22 08:20:34 +02:00
Dimitri Herzog e6b41db553 feat: per-client DNS rate limiting (#2063) 2026-05-20 11:58:35 +02:00
Dimitri Herzog 842dda99a8 feat: serve DoH over HTTP/3 (DoH3, RFC 9114) (#2060) 2026-05-18 21:57:15 +02:00
Dimitri Herzog 667044b07b refactor: redis write through cache (#2025) 2026-03-28 21:59:26 +01:00
Dimitri Herzog a2f197a55f test: add tests 2026-03-23 15:54:17 +01:00
Dimitri Herzog cb2ae25341 build: update golangci-lint (#2008) 2026-03-10 22:13:45 +01:00
Dimitri Herzog fb285134de fix: enhance DNS bootstrapping by utilizing IPs from DNS stamps (#1995) 2026-03-02 21:33:13 +01:00
Yevhen PavlovandDimitri Herzog 98742121da fix: use context based versions of net.Listen and net.Dial (#1952)
Co-authored-by: Dimitri Herzog <[email protected]>
2026-01-23 07:55:37 +00:00
Dimitri Herzog 011e3eb500 feat: implement DNS64 support (#1933) 2026-01-08 08:15:06 +01:00
Yevhen Pavlov 3075c3810c refactor: clean up obsolete Go syntax (#1951) 2025-12-03 06:54:53 +01:00
Dimitri Herzog 9fe42a8621 refactor: simplify code for improved readability and maintainability (#1945) 2025-11-22 21:55:46 +01:00
Dimitri Herzog acbf95352d test: small test optimizations 2025-11-07 22:07:06 +01:00
Dimitri Herzog 77578dafdf feat: add DNSSEC validation (#1914) 2025-11-07 21:12:05 +01:00
Dimitri Herzog 3a83dbe97c test: add additional tests 2025-10-24 21:19:41 +02:00
Tim Quelch 8aad53ea47 fix: TCP response truncation (#1904)
Fix TCP response truncation

Currently a TCP response is truncated to the maximum size advertised for
UDP. This means that a client cannot effectively fallback to TCP when
receiving at truncated UDP response, as the resulting TCP response is
also truncated.

This change ensures that only UDP responses are truncated
2025-10-24 19:13:59 +00:00
Dimitri Herzog ab77fd57cc build: update tool dependencies (#1898) 2025-10-20 21:29:35 +02:00
Dimitri Herzog d0681ae6d3 feat: enhance error messages with more context (#1894) 2025-10-18 20:51:28 +02:00
Dimitri Herzog 0804760319 fix: resolve panic when CNAME points to external domain (#1867) (#1884)
Fixed a nil pointer dereference panic that occurred when
CustomDNSResolver returned a CNAME record pointing to an external
domain. The bug was caused by RewriterResolver's branching architecture
isolating resolvers with NoOpResolver, preventing proper CNAME chain
resolution.
2025-10-11 22:47:52 +02:00
Dimitri Herzog d89f56bb46 chore: fix Cache-Control header capitalization 2025-10-06 21:34:49 +02:00
Dimitri Herzog a2cfb0b535 feat: add robots.txt (#1864) 2025-09-10 21:16:33 +02:00
Dimitri Herzog 9f4db47a6e feat: make DoH query path configurable (#1843) 2025-08-26 21:52:54 +02:00
Dimitri Herzog 353b4e5b81 build: add additional linters 2025-08-08 23:20:29 +02:00
Dimitri Herzog 9ec43b5436 build: upgrade golangci-lint 2025-08-04 22:42:16 +02:00
Dimitri Herzog 262cd2e984 fix: prevent DoS in DOH endpoits (#1826) 2025-08-04 22:37:59 +02:00
macsign 6674c55b01 feat: add cache exclude configuration (#1753) 2025-05-20 21:10:49 +02:00
Dimitri Herzog 500195da05 test: fix tests after deps upgrade 2025-05-20 21:01:52 +02:00
Dimitri Herzog 3a449b05f3 feat: add "cache-control" http header to DoH response (#1732) 2025-03-09 22:36:53 +01:00
Dimitri Herzog 924aa16feb test: Improve e2e tests (#1728) 2025-03-06 21:36:57 +01:00
TheoTechnicguy 49581ff40d feat: configurable request header read timeout (#1664)
* feat(config): read read header timeout value

This commit adds a request header read timeout configuration parameter.
As it is part of the downloading cycle, it is available under
`blocking/loading/downloads`.
The default value comes from `server/http.go` line 21.

Refs: #1653

* feat(server): get read header timeout from config

* feat(docs): document additional config parameter
2025-01-01 22:34:30 +01:00
TheoTechnicguy 0d00311513 feat: configurable request read timeouts (#1663)
* feat(config): read read timeout value

This commit adds a request read timeout configuration parameter. As it
is part of the downloading cycle, it is available under
`blocking/loading/downloads`.
The default value comes from `server/http.go` line 22.

Refs: #1653

* feat(server): get read timeout from config

* feat(docs): document additional config parameter

* refactor(server): remove superfluous spaces
2024-12-23 21:39:50 +01:00
TheoTechnicguyandDimitri Herzog e485df1d4f feat: configurable file write timeouts (#1653)
* feat(config): read write timeout value

This commit adds a write timeout configuration parameter. As it is part
of the downloading cycle, it is available under
`blocking/loading/downloads`.
The default value comes from `server/http.go` line 23.

Refs: #1606

* feat(server): get write timeout from config

Refs: #1606

* feat(docs): document additional config parameter

---------

Co-authored-by: Dimitri Herzog <[email protected]>
2024-12-17 12:17:29 +01:00
ThinkChaos 6aae002ca7 refactor: add : prefix to ports during config unmarshaling 2024-09-04 16:12:29 +00:00
ThinkChaos 70afa43749 refactor: make QueryLoggingResolver read the hostname on creation
Remove that code from util and the use of globals.

Partly move away from calling the value a "hostname" since it's more of
an instance ID, especially if you run more than one per host.
2024-09-04 16:12:29 +00:00