Codebase list dnss / 9c203de
httpserver: Implement a DoH (DNS over HTTPS) handler This patch adds a DoH handler to the HTTPS-to-DNS server. The handler is defined according to the current DoH draft, https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-05. It's still experimental and will likely change in the future. Alberto Bertogli 6 years ago
1 changed file(s) with 127 addition(s) and 8 deletion(s). Raw diff Collapse all Expand all
00 // Package httpserver implements an HTTPS server which handles DNS requests
11 // over HTTPS.
2 //
3 // It implements:
4 // - Google's DNS over HTTPS using JSON (dns-json), as specified in:
5 // https://developers.google.com/speed/public-dns/docs/dns-over-https#api_specification.
6 // This is also implemented by Cloudflare's 1.1.1.1, as documented in:
7 // https://developers.cloudflare.com/1.1.1.1/dns-over-https/json-format/.
8 // - DNS Queries over HTTPS (DoH), as specified in:
9 // https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-05.
210 package httpserver
311
412 import (
13 "encoding/base64"
514 "encoding/json"
615 "fmt"
16 "io"
17 "io/ioutil"
18 "mime"
719 "net"
820 "net/http"
921 "net/url"
1729 "golang.org/x/net/trace"
1830 )
1931
20 // Server is an HTTPS server that implements DNS over HTTPS, as specified in
21 // https://developers.google.com/speed/public-dns/docs/dns-over-https#api_specification.
32 // Server is an HTTPS server that implements DNS over HTTPS, see the
33 // package-level documentation for more references.
2234 type Server struct {
2335 Addr string
2436 Upstream string
3345 // ListenAndServe starts the HTTPS server.
3446 func (s *Server) ListenAndServe() {
3547 mux := http.NewServeMux()
48 mux.HandleFunc("/dns-query", s.Resolve)
3649 mux.HandleFunc("/resolve", s.Resolve)
3750 srv := http.Server{
3851 Addr: s.Addr,
4962 glog.Fatalf("HTTPS exiting: %s", err)
5063 }
5164
52 // Resolve "DNS over HTTPS" requests, and returns responses as specified in
53 // https://developers.google.com/speed/public-dns/docs/dns-over-https#api_specification.
54 // It implements an http.HandlerFunc so it can be used with any standard Go
55 // HTTP server.
65 // Resolve implements the HTTP handler for incoming DNS resolution requests.
66 // It handles "Google's DNS over HTTPS using JSON" requests, as well as "DoH"
67 // request.
5668 func (s *Server) Resolve(w http.ResponseWriter, req *http.Request) {
5769 tr := trace.New("httpserver", "/resolve")
5870 defer tr.Finish()
59
6071 tr.LazyPrintf("from:%v", req.RemoteAddr)
61
72 tr.LazyPrintf("method:%v", req.Method)
73
74 req.ParseForm()
75
76 // Identify DoH requests:
77 // - GET requests have a "dns=" query parameter.
78 // - POST requests have a content-type = application/dns-udpwireformat.
79 if req.Method == "GET" && req.FormValue("dns") != "" {
80 tr.LazyPrintf("DoH:GET")
81 dnsQuery, err := base64.RawURLEncoding.DecodeString(
82 req.FormValue("dns"))
83 if err != nil {
84 util.TraceError(tr, err)
85 http.Error(w, err.Error(), http.StatusBadRequest)
86 return
87 }
88
89 s.resolveDoH(tr, w, dnsQuery)
90 return
91 }
92
93 if req.Method == "POST" {
94 ct, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
95 if err != nil {
96 util.TraceError(tr, err)
97 http.Error(w, err.Error(), http.StatusBadRequest)
98 return
99 }
100
101 if ct == "application/dns-udpwireformat" {
102 tr.LazyPrintf("DoH:POST")
103 // Limit the size of request to 4k.
104 dnsQuery, err := ioutil.ReadAll(io.LimitReader(req.Body, 4092))
105 if err != nil {
106 util.TraceError(tr, err)
107 http.Error(w, err.Error(), http.StatusBadRequest)
108 return
109 }
110
111 s.resolveDoH(tr, w, dnsQuery)
112 return
113 }
114 }
115
116 // Fall back to Google's JSON, the laxer format.
117 // It MUST have a "name" query parameter, so we use that for detection.
118 if req.Method == "GET" && req.FormValue("name") != "" {
119 tr.LazyPrintf("Google-JSON")
120 s.resolveJSON(tr, w, req)
121 return
122 }
123
124 // Could not found how to handle this request.
125 util.TraceErrorf(tr, "unknown request type")
126 http.Error(w, "unknown request type", http.StatusUnsupportedMediaType)
127 }
128
129 // Resolve "Google's DNS over HTTPS using JSON" requests, and returns
130 // responses as specified in
131 // https://developers.google.com/speed/public-dns/docs/dns-over-https#api_specification.
132 func (s *Server) resolveJSON(tr trace.Trace, w http.ResponseWriter, req *http.Request) {
62133 // Construct the DNS request from the http query.
63134 q, err := parseQuery(req.URL)
64135 if err != nil {
256327
257328 return false, errInvalidCD
258329 }
330
331 // Resolve DNS over HTTPS requests, as specified in
332 // https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-05.
333 func (s *Server) resolveDoH(tr trace.Trace, w http.ResponseWriter, dnsQuery []byte) {
334 r := &dns.Msg{}
335 err := r.Unpack(dnsQuery)
336 if err != nil {
337 util.TraceError(tr, err)
338 http.Error(w, err.Error(), http.StatusBadRequest)
339 return
340 }
341
342 util.TraceQuestion(tr, r.Question)
343
344 // Do the DNS request, get the reply.
345 fromUp, err := dns.Exchange(r, s.Upstream)
346 if err != nil {
347 err = util.TraceErrorf(tr, "dns exchange error: %v", err)
348 http.Error(w, err.Error(), http.StatusFailedDependency)
349 return
350 }
351
352 if fromUp == nil {
353 err = util.TraceErrorf(tr, "no response from upstream")
354 http.Error(w, err.Error(), http.StatusRequestTimeout)
355 return
356 }
357
358 util.TraceAnswer(tr, fromUp)
359
360 packed, err := fromUp.Pack()
361 if err != nil {
362 err = util.TraceErrorf(tr, "cannot pack reply: %v", err)
363 http.Error(w, err.Error(), http.StatusFailedDependency)
364 return
365 }
366
367 // Write the response back.
368 w.Header().Set("Content-type", "application/dns-udpwireformat")
369 // TODO: set cache-control based on the response.
370 w.WriteHeader(http.StatusOK)
371 w.Write(packed)
372 }
373
374 func parseContentType(s string) (string, error) {
375 mt, _, err := mime.ParseMediaType(s)
376 return mt, err
377 }