1+ // Package handler provides all handling logic for DNS queries over both standard (raw TCP and UDP) and encrypted
2+ // (DNS-over-TLS, DNS-over-HTTPs) protocols. It includes the allow/block listing logic as well as upstream weighting
3+ // by round-trip-time.
14package handler
25
6+ // Throughout this package are comments that link specific behavior to DNS-related RFCs. These RFCs can be read at:
7+ // * RFC-1035 (Core DNS): https://www.rfc-editor.org/rfc/rfc1035.html
8+ // * RFC-6891 (EDNS0): https://www.rfc-editor.org/rfc/rfc6891.html
9+ // * RFC-4035 (DNSSEC signaling): https://www.rfc-editor.org/rfc/rfc4035.html
10+ // * RFC-8484 (DNS over HTTPS): https://www.rfc-editor.org/rfc/rfc8484.html
311import (
412 "context"
513 "encoding/base64"
@@ -49,6 +57,28 @@ func New(allow, block *set.Set[string], upstreams []string, logger *slog.Logger)
4957 }
5058}
5159
60+ var (
61+ // We need to append this to the "Extra" section of the response whenever we have an EDNS0 version that we
62+ // do not support, so we declare it once here and pass it into Handler.dnsError when required.
63+ badVersionOpt = & dns.OPT {
64+ Hdr : dns.RR_Header {
65+ Name : "." ,
66+ Rrtype : dns .TypeOPT ,
67+ },
68+ }
69+ )
70+
71+ func init () {
72+ // A little dirty, but we also need to specify the version and set our payload size once for this helpful
73+ // variable.
74+ badVersionOpt .SetVersion (0 )
75+ badVersionOpt .SetUDPSize (udpPayloadSize )
76+ }
77+
78+ const (
79+ udpPayloadSize = 1232
80+ )
81+
5282// ServeDNS handles an inbound DNS request attempting to perform a DNS query using UDP or TCP.
5383func (h * Handler ) ServeDNS (w dns.ResponseWriter , r * dns.Msg ) {
5484 ctx , cancel := context .WithTimeout (context .Background (), time .Minute )
@@ -57,15 +87,23 @@ func (h *Handler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
5787 response := new (dns.Msg )
5888 response .SetReply (r )
5989
90+ // RFC-6891 (6.1.3): If a responder does not implement the requested EDNS version,it MUST respond with RCODE=BADVERS.
91+ if opt := r .IsEdns0 (); opt != nil && opt .Version () != 0 {
92+ h .dnsError (w , r , dns .RcodeBadVers , badVersionOpt )
93+ return
94+ }
95+
96+ // RFC-1035 (4.1.2): While the protocol allows multiple questions per message, most resolvers do not support this
97+ // and return NOTIMP.
6098 if len (r .Question ) != 1 {
61- // Handling multiple questions per request is typically not supported.
6299 h .dnsError (w , r , dns .RcodeNotImplemented )
63100 return
64101 }
65102
66103 question := r .Question [0 ]
67104 name := strings .TrimSuffix (strings .ToLower (question .Name ), "." )
68105
106+ // RFC-1035 (4.3.1): NXDOMAIN indicates that the domain name does not exist. Used here for policy-based blocking.
69107 if h .block .Contains (name ) && ! h .allow .Contains (name ) {
70108 h .dnsError (w , r , dns .RcodeNameError )
71109 return
@@ -86,6 +124,7 @@ func (h *Handler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
86124func (h * Handler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
87125 const maxBytes = 4096
88126
127+ // RFC-8484 (4): DNS-over-HTTPS supports both GET and POST methods.
89128 if r .Method != http .MethodPost && r .Method != http .MethodGet {
90129 http .Error (w , http .StatusText (http .StatusMethodNotAllowed ), http .StatusMethodNotAllowed )
91130 return
@@ -106,6 +145,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
106145 // DNS-over-HTTPs allows both POST and GET requests. If we're using a POST request we'll want to read directly
107146 // from the request body.
108147 if r .Method == http .MethodPost {
148+ // RFC-8484 (4.1): POST requests MUST use Content-Type: application/dns-message.
109149 if r .Header .Get ("Content-Type" ) != "application/dns-message" {
110150 http .Error (w , http .StatusText (http .StatusUnsupportedMediaType ), http .StatusUnsupportedMediaType )
111151 return
@@ -134,6 +174,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
134174 return
135175 }
136176
177+ // RFC-8484 (4.1): GET requests encode the DNS message using base64 in the "dns" query parameter.
137178 data , err = base64 .RawURLEncoding .DecodeString (query )
138179 if err != nil {
139180 http .Error (w , http .StatusText (http .StatusBadRequest ), http .StatusBadRequest )
@@ -146,11 +187,18 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
146187 return
147188 }
148189
149- // From this point on, we're always responding with an HTTP 200 and the DNS-encoded messages, so we set the headers
150- // now for all possible DNS responses.
190+ // RFC-8484 (5): DNS responses are always returned with HTTP 200.
151191 w .Header ().Set ("Content-Type" , "application/dns-message" )
152192 w .Header ().Set ("Cache-Control" , "no-store" )
153193
194+ // RFC-6891 (6.1.3): If a responder does not implement the requested EDNS version, it MUST respond with BADVERS.
195+ if opt := req .IsEdns0 (); opt != nil && opt .Version () != 0 {
196+ h .dnsError (w , req , dns .RcodeBadVers , badVersionOpt )
197+ return
198+ }
199+
200+ // RFC-1035 (4.1.2): While the protocol allows multiple questions per message, most resolvers do not support this
201+ // and return NOTIMP.
154202 if len (req .Question ) != 1 {
155203 h .dnsError (w , req , dns .RcodeNotImplemented )
156204 return
@@ -159,6 +207,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
159207 question := req .Question [0 ]
160208 name := strings .TrimSuffix (strings .ToLower (question .Name ), "." )
161209
210+ // RFC-1035 (4.3.1): NXDOMAIN indicates that the domain name does not exist. Used here for policy-based blocking.
162211 if h .block .Contains (name ) && ! h .allow .Contains (name ) {
163212 h .dnsError (w , req , dns .RcodeNameError )
164213 return
@@ -181,11 +230,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
181230 }
182231}
183232
184- func (h * Handler ) dnsError (w io.Writer , r * dns.Msg , code int ) {
233+ func (h * Handler ) dnsError (w io.Writer , r * dns.Msg , code int , extra ... dns. RR ) {
185234 response := new (dns.Msg )
186235 response .SetReply (r )
187236 response .SetRcode (r , code )
188237
238+ response .Extra = append (response .Extra , extra ... )
239+
189240 data , err := response .Pack ()
190241 if err != nil {
191242 // We panic here as we really should never be in this situation unless something has really gone wrong. There
@@ -200,10 +251,8 @@ func (h *Handler) dnsError(w io.Writer, r *dns.Msg, code int) {
200251}
201252
202253func (h * Handler ) dnsUpstream (ctx context.Context , r * dns.Msg ) (* dns.Msg , error ) {
203- const udpSize = 1232
204-
205- // Before we upstream a DNS request, we'll cap the UDP size to prevent the upstream from sending large
206- // or fragmented messages.
254+ // RFC-6891 (6.2.3): EDNS allows clients to advertise a larger UDP payload size. We cap this to 1232 bytes to avoid
255+ // IP fragmentation.
207256 opt := r .IsEdns0 ()
208257 if opt == nil {
209258 opt = & dns.OPT {
@@ -216,8 +265,8 @@ func (h *Handler) dnsUpstream(ctx context.Context, r *dns.Msg) (*dns.Msg, error)
216265 r .Extra = append (r .Extra , opt )
217266 }
218267
219- if opt .UDPSize () < udpSize {
220- opt .SetUDPSize (udpSize )
268+ if opt .UDPSize () < udpPayloadSize {
269+ opt .SetUDPSize (udpPayloadSize )
221270 }
222271
223272 // Iterate over the upstreams in ascending order of most recent round-trip-time.
@@ -230,7 +279,7 @@ func (h *Handler) dnsUpstream(ctx context.Context, r *dns.Msg) (*dns.Msg, error)
230279 continue
231280 }
232281
233- // The upstream has given us a truncated response, so we need to switch over to TCP to get the full response .
282+ // RFC-1035 (4.2.2): If the TC ( truncated) bit is set, the client should retry using TCP.
234283 if resp .Truncated {
235284 resp , _ , err = h .tcpClient .ExchangeContext (ctx , r , upstream )
236285 if err != nil {
@@ -253,17 +302,19 @@ func (h *Handler) dnsUpstream(ctx context.Context, r *dns.Msg) (*dns.Msg, error)
253302 continue
254303 }
255304
256- // We want to avoid sending any EDNS0 options from the upstream to avoid leaking any upstream
257- // metadata.
305+ // RFC-6891 (6.1.1): EDNS0 options are hop-by-hop and MUST NOT be blindly forwarded. We strip upstream options
306+ // to avoid leaking upstream metadata.
258307 opt = resp .IsEdns0 ()
259308 if opt != nil {
260309 opt .Option = nil
261310 }
262311
263- // We need to update the flags to correspond with what this DNS server provides, rather than the
264- // upstream itself.
312+ // RFC-{1035,4035}: This server is not authoritative but does provide recursion.
265313 resp .Authoritative = false
266314 resp .RecursionAvailable = true
315+
316+ // RFC-4035 (3.2.3): The AD (Authenticated Data) bit MUST only be set by a resolver that has performed DNSSEC
317+ // validation itself.
267318 resp .AuthenticatedData = false
268319
269320 return resp , nil
0 commit comments