cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2019.3.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

tunneldns/https_upstream.go

106lines · modecode

1package tunneldns
2
3import (
4 "bytes"
5 "context"
6 "crypto/tls"
7 "fmt"
8 "io/ioutil"
9 "net/http"
10 "net/url"
11 "time"
12
13 "github.com/miekg/dns"
14 "github.com/pkg/errors"
15 log "github.com/sirupsen/logrus"
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 Proxy: http.ProxyFromEnvironment,
43 }
44 http2.ConfigureTransport(transport)
45
46 client := &http.Client{
47 Timeout: defaultTimeout,
48 Transport: transport,
49 }
50
51 return &UpstreamHTTPS{client: client, endpoint: u}, nil
52}
53
54// Exchange provides an implementation for the Upstream interface
55func (u *UpstreamHTTPS) Exchange(ctx context.Context, query *dns.Msg) (*dns.Msg, error) {
56 queryBuf, err := query.Pack()
57 if err != nil {
58 return nil, errors.Wrap(err, "failed to pack DNS query")
59 }
60
61 // No content negotiation for now, use DNS wire format
62 buf, backendErr := u.exchangeWireformat(queryBuf)
63 if backendErr == nil {
64 response := &dns.Msg{}
65 if err := response.Unpack(buf); err != nil {
66 return nil, errors.Wrap(err, "failed to unpack DNS response from body")
67 }
68
69 response.Id = query.Id
70 return response, nil
71 }
72
73 log.WithError(backendErr).Errorf("failed to connect to an HTTPS backend %q", u.endpoint)
74 return nil, backendErr
75}
76
77// Perform message exchange with the default UDP wireformat defined in current draft
78// https://datatracker.ietf.org/doc/draft-ietf-doh-dns-over-https
79func (u *UpstreamHTTPS) exchangeWireformat(msg []byte) ([]byte, error) {
80 req, err := http.NewRequest("POST", u.endpoint.String(), bytes.NewBuffer(msg))
81 if err != nil {
82 return nil, errors.Wrap(err, "failed to create an HTTPS request")
83 }
84
85 req.Header.Add("Content-Type", "application/dns-udpwireformat")
86 req.Host = u.endpoint.Hostname()
87
88 resp, err := u.client.Do(req)
89 if err != nil {
90 return nil, errors.Wrap(err, "failed to perform an HTTPS request")
91 }
92
93 // Check response status code
94 defer resp.Body.Close()
95 if resp.StatusCode != http.StatusOK {
96 return nil, fmt.Errorf("returned status code %d", resp.StatusCode)
97 }
98
99 // Read wireformat response from the body
100 buf, err := ioutil.ReadAll(resp.Body)
101 if err != nil {
102 return nil, errors.Wrap(err, "failed to read the response body")
103 }
104
105 return buf, nil
106}
107