cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2018.9.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

tunneldns/https_upstream.go

105lines · modecode

1package tunneldns
2
3import (
4 "bytes"
5 "crypto/tls"
6 "fmt"
7 "io/ioutil"
8 "net/http"
9 "net/url"
10 "time"
11
12 "github.com/miekg/dns"
13 "github.com/pkg/errors"
14 log "github.com/sirupsen/logrus"
15 "golang.org/x/net/context"
16 "golang.org/x/net/http2"
17)
18
19const (
20 defaultTimeout = 5 * time.Second
21)
22
23// UpstreamHTTPS is the upstream implementation for DNS over HTTPS service
24type UpstreamHTTPS struct {
25 client *http.Client
26 endpoint *url.URL
27}
28
29// NewUpstreamHTTPS creates a new DNS over HTTPS upstream from hostname
30func NewUpstreamHTTPS(endpoint string) (Upstream, error) {
31 u, err := url.Parse(endpoint)
32 if err != nil {
33 return nil, err
34 }
35
36 // Update TLS and HTTP client configuration
37 tls := &tls.Config{ServerName: u.Hostname()}
38 transport := &http.Transport{
39 TLSClientConfig: tls,
40 DisableCompression: true,
41 MaxIdleConns: 1,
42 }
43 http2.ConfigureTransport(transport)
44
45 client := &http.Client{
46 Timeout: defaultTimeout,
47 Transport: transport,
48 }
49
50 return &UpstreamHTTPS{client: client, endpoint: u}, nil
51}
52
53// Exchange provides an implementation for the Upstream interface
54func (u *UpstreamHTTPS) Exchange(ctx context.Context, query *dns.Msg) (*dns.Msg, error) {
55 queryBuf, err := query.Pack()
56 if err != nil {
57 return nil, errors.Wrap(err, "failed to pack DNS query")
58 }
59
60 // No content negotiation for now, use DNS wire format
61 buf, backendErr := u.exchangeWireformat(queryBuf)
62 if backendErr == nil {
63 response := &dns.Msg{}
64 if err := response.Unpack(buf); err != nil {
65 return nil, errors.Wrap(err, "failed to unpack DNS response from body")
66 }
67
68 response.Id = query.Id
69 return response, nil
70 }
71
72 log.WithError(backendErr).Errorf("failed to connect to an HTTPS backend %q", u.endpoint)
73 return nil, backendErr
74}
75
76// Perform message exchange with the default UDP wireformat defined in current draft
77// https://datatracker.ietf.org/doc/draft-ietf-doh-dns-over-https
78func (u *UpstreamHTTPS) exchangeWireformat(msg []byte) ([]byte, error) {
79 req, err := http.NewRequest("POST", u.endpoint.String(), bytes.NewBuffer(msg))
80 if err != nil {
81 return nil, errors.Wrap(err, "failed to create an HTTPS request")
82 }
83
84 req.Header.Add("Content-Type", "application/dns-udpwireformat")
85 req.Host = u.endpoint.Hostname()
86
87 resp, err := u.client.Do(req)
88 if err != nil {
89 return nil, errors.Wrap(err, "failed to perform an HTTPS request")
90 }
91
92 // Check response status code
93 defer resp.Body.Close()
94 if resp.StatusCode != http.StatusOK {
95 return nil, fmt.Errorf("returned status code %d", resp.StatusCode)
96 }
97
98 // Read wireformat response from the body
99 buf, err := ioutil.ReadAll(resp.Body)
100 if err != nil {
101 return nil, errors.Wrap(err, "failed to read the response body")
102 }
103
104 return buf, nil
105}