Commit b2b7a96
committed
dotdoh: add DNS-over-QUIC (RFC 9250) up- and downstream
DoQ is the last IETF standards-track encrypted DNS transport we did not speak,
and the cheapest one to add: the DNS message rides a QUIC stream with the same
2-byte length prefix DoT already uses, so on top of the OpenSSL QUIC stack the
DoH3 work brought in there is no HTTP layer at all. Both directions land
together because they share that stack.
1. Outbound: `doq://host` becomes a `dns.upstreams` scheme (the `quic://`
spelling AdGuard and dnsproxy configs use is accepted as an alias),
defaulting to UDP port 853. The exchange is fail-closed exactly like
DoT/DoH/DoH3 - a bad chain, a reset stream or a timeout drops the query so
dnsmasq fails over rather than downgrading to plaintext. Per RFC 9250
Sec. 4.2.1 the Message ID goes out as 0 and dnsmasq's own ID is restored on
the answer.
2. Inbound: `dns.doq` (default on, like `dns.dot`) brings up a listener on
UDP/853 - the same port number DoT uses on TCP, which does not collide. It is
a single-threaded event loop in the shape of the DoT listener: OpenSSL owns
the QUIC transport while every in-flight query is a small non-blocking state
machine, so a slow resolve never stalls another connection and a flood costs
a bounded state record rather than a thread. Queries are attributed to the
real downstream client through the same private-EDNS handoff as DoT/DoH, and
answers are padded per RFC 8467 when the client asked for it.
3. The socket, handshake and timer plumbing the DoH3 client already carried
moves into `quic_common.c`, so both QUIC clients share one context, one trust
store and one fail-closed verify instead of duplicating them. DoQ is gated on
the new `HAVE_QUIC` (OpenSSL >= 4.0) rather than `HAVE_HTTP3`, so a build
without nghttp3 still speaks it.
Worth calling out on the hardening side: QUIC address validation (Retry) stays
on so we cannot be used to amplify towards a forged source, 0-RTT is explicitly
disabled because a replayed early-data query would be answered and logged twice,
only the `doq` ALPN is accepted, and connections, per-source connections,
concurrent streams and per-connection queries are each capped. RFC 9250
Sec. 5.5.2 makes `edns-tcp-keepalive` a protocol error on DoQ, so a query
carrying it closes the connection.
One wrinkle needed solving: OpenSSL's QUIC API exposes the peer address but no
per-connection *local* address, and the listener is wildcard-bound, so - unlike
DoT and DoH - we cannot simply read off which of our addresses the client
reached. Conveying nothing is not an option: the answer would then be built from
the interface of our own loopback handoff, so `pi.hole` would resolve to
127.0.0.1 for every DoQ client, and a CNAME chain reaching it would write that
into the shared cache record. We therefore ask the kernel which source address it
would use to reach that peer - the address it would itself put on a reply
datagram, and so the one a plain-DNS answer is built from - and convey that.
Signed-off-by: DL6ER <dl6er@dl6er.de>1 parent 48e0271 commit b2b7a96
30 files changed
Lines changed: 2724 additions & 306 deletions
File tree
- src
- api/docs/content/specs
- config
- dotdoh
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
594 | 594 | | |
595 | 595 | | |
596 | 596 | | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
597 | 608 | | |
598 | 609 | | |
599 | 610 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
301 | 301 | | |
302 | 302 | | |
303 | 303 | | |
| 304 | + | |
| 305 | + | |
304 | 306 | | |
305 | 307 | | |
306 | 308 | | |
| |||
775 | 777 | | |
776 | 778 | | |
777 | 779 | | |
| 780 | + | |
778 | 781 | | |
779 | 782 | | |
780 | 783 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
604 | 604 | | |
605 | 605 | | |
606 | 606 | | |
607 | | - | |
| 607 | + | |
608 | 608 | | |
609 | 609 | | |
610 | 610 | | |
| |||
625 | 625 | | |
626 | 626 | | |
627 | 627 | | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
628 | 635 | | |
629 | 636 | | |
630 | 637 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
158 | 158 | | |
159 | 159 | | |
160 | 160 | | |
| 161 | + | |
161 | 162 | | |
162 | 163 | | |
163 | 164 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
810 | 810 | | |
811 | 811 | | |
812 | 812 | | |
813 | | - | |
814 | | - | |
| 813 | + | |
| 814 | + | |
815 | 815 | | |
816 | 816 | | |
817 | 817 | | |
| |||
825 | 825 | | |
826 | 826 | | |
827 | 827 | | |
828 | | - | |
829 | | - | |
830 | | - | |
831 | | - | |
832 | | - | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
833 | 833 | | |
834 | 834 | | |
835 | 835 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3746 | 3746 | | |
3747 | 3747 | | |
3748 | 3748 | | |
| 3749 | + | |
| 3750 | + | |
| 3751 | + | |
| 3752 | + | |
| 3753 | + | |
| 3754 | + | |
| 3755 | + | |
| 3756 | + | |
3749 | 3757 | | |
3750 | 3758 | | |
3751 | 3759 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
| 26 | + | |
| 27 | + | |
24 | 28 | | |
25 | 29 | | |
26 | 30 | | |
27 | 31 | | |
28 | 32 | | |
29 | 33 | | |
30 | 34 | | |
| 35 | + | |
31 | 36 | | |
32 | 37 | | |
33 | 38 | | |
| |||
0 commit comments