You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
dotdoh: harden the DoQ listener and bound the dnsmasq children it occupies
Follow-up hardening on the inbound DoQ work, from review of the listener and of
how the three encrypted paths share the embedded dnsmasq.
Protocol handling:
- Trailing bytes on a DoQ stream were discarded silently. RFC 9250, Sec. 4.2
gives each query its own stream, so anything after that one message is a
protocol violation; the stream is now reset with `DOQ_PROTOCOL_ERROR` rather
than letting a non-conformant client smuggle a second message past us. Not
requiring the client's FIN before answering stays deliberate.
- `SO_REUSEADDR` is gone from the UDP listener. UDP has no `TIME_WAIT` to work
around, and with the flag a second daemon binds 853 successfully while the
kernel delivers the datagrams to only one of us. Without it the clash surfaces
as `EADDRINUSE`.
- A pooled loopback socket the peer closed between the checkout probe and our
write is now retried once on the write side, as the DoT path already did on
the read side.
- `ossl_err()` reported the oldest queued OpenSSL error rather than the most
recent, so an unrelated stale entry could be printed in place of the failure
being described.
Concurrency:
Every in-flight query occupies one of dnsmasq's TCP children, of which it serves
60 by default (`--max-tcp-connections`). Nothing bounded our share: DoT allowed
one per connection, DoQ one per stream and DoH one per webserver thread, so the
three could ask for far more than exist and starve plain TCP queries and DNSSEC
fallback host-wide.
Admission is now counted in `dotdoh_loopback_take()`, which every path already
calls before obtaining a socket. It reserves a slot, or returns -2 once the limit
is reached so the caller can refuse the query rather than queue it - DoQ resets
the stream with `DOQ_EXCESSIVE_LOAD` so the client knows to back off. The new
`dotdoh_loopback_drop()` releases a socket that must not be reused and frees the
slot, so the error paths account as `dotdoh_loopback_give()` already did.
The limit is derived from `daemon->max_procs` rather than configured, leaving
`LOOPBACK_RESERVE` children for plain TCP: raising dnsmasq's own limit raises
ours, with no second setting to keep in step. Refusals are logged at most once a
minute with a count, naming both numbers, as a per-query line would flood the log
under exactly the overload that produces it.
DoH held a loopback connection per webserver thread for the life of that thread,
pinning a child whether or not it was serving anything and sitting outside any
accounting; DoT held one for the life of each keep-alive connection. Both now
borrow one per query, so idle clients occupy no slot, and the pool is kept as
large as the limit so a query at full concurrency still finds a warm socket
instead of forking. A blocking exchange also arms its own send and receive
timeouts now: the reactors create their sockets without any, correctly, as they
never block on them, and a borrowed one would otherwise be bounded only by
dnsmasq's 300 s child lifetime.
Also documents the `quic://` alias of `doq://`, which `parse_upstream_uri()` has
always accepted, in the `dns.upstreamCA` help text and both `validate_upstreams()`
comments.
Signed-off-by: DL6ER <dl6er@dl6er.de>
conf->dns.upstreamCA.h="Path to a CA certificate bundle used to verify encrypted upstream servers (DoT/DoH). If left empty, the system default trust store is used. Only relevant when at least one dns.upstreams entry uses an encrypted scheme (tls://, https://, h3://or doq://).";
607
+
conf->dns.upstreamCA.h="Path to a CA certificate bundle used to verify encrypted upstream servers (DoT/DoH). If left empty, the system default trust store is used. Only relevant when at least one dns.upstreams entry uses an encrypted scheme (tls://, https://, h3://, doq:// or quic://).";
608
608
conf->dns.upstreamCA.a=cJSON_CreateStringReference("A path to a PEM CA bundle, or empty for the system default trust store");
0 commit comments