-
Notifications
You must be signed in to change notification settings - Fork 0
/
client.go
153 lines (130 loc) · 3.8 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package main
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"github.com/pascaldierich/doh-reference-client/lib"
)
// media type for DoH is defined as following. (see: section 6)
const mediaType = "application/dns-message"
// "The DoH client SHOULD include an HTTP 'Accept' request header
// field to indicate what type of content can be understood in response."
// (see: section 4.1)
var setAcceptHeader = func(req *http.Request) {
req.Header.Set("Accept", mediaType)
}
// Sends a DNS-over-HTTP POST request and returns the answered RR's
// or error if any.
func sendPOSTRequest(server, addr string) ([]*lib.RR, error) {
payload, err := createDNSMessage(addr)
if err != nil {
return nil, err
}
// "When using the POST method the DNS query is included as the message
// body of the HTTP request and the Content-Type request header field
// indicates the media type of the message."
// (see: secttion 4.1)
req, err := http.NewRequest(http.MethodPost, server, bytes.NewBuffer(payload))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", mediaType)
setAcceptHeader(req)
c := &http.Client{}
resp, err := c.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// "A successful HTTP response with a 2xx status code is used for any
// valid DNS response, regardless of the DNS response code."
// (see: section 4.2.1)
if resp.StatusCode != http.StatusOK {
tmp := fmt.Sprintf("bad response Code: %v", resp.StatusCode)
return nil, errors.New(tmp)
}
msg, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
responseMsg := &lib.Message{}
err = lib.UnmarshalMessage(msg, responseMsg)
if err != nil {
return nil, err
}
return responseMsg.Answers, nil
}
// "When the HTTP method is GET the single variable 'dns' is defined as
// the content of the DNS request". (see: section 4.1)
const DNSRequestContent = "dns"
// Sends a DNS-over-HTTP GET request and returns the answered RR's
// or error if any.
func sendGETRequest(server, addr string) ([]*lib.RR, error) {
payload, err := createDNSMessage(addr)
if err != nil {
return nil, err
}
// "When using the GET method, the data payload [...] MUST be encoded
// with base64url...". (see: section 6)
enc := base64.RawURLEncoding.EncodeToString(payload)
// "...and then provided as a variable named 'dns' to the URI Template
// expansion. (see: section 6)
url := fmt.Sprintf("%s?%s=%s", server, DNSRequestContent, enc)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", mediaType)
setAcceptHeader(req)
c := &http.Client{}
resp, err := c.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// "A successful HTTP response with a 2xx status code is used for any
// valid DNS response, regardless of the DNS response code."
// (see: section 4.2.1)
if resp.StatusCode != http.StatusOK {
tmp := fmt.Sprintf("bad response Code: %v", resp.StatusCode)
return nil, errors.New(tmp)
}
msg, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
responseMsg := &lib.Message{}
err = lib.UnmarshalMessage(msg, responseMsg)
if err != nil {
return nil, err
}
return responseMsg.Answers, nil
}
// Returns raw DNS message.
func createDNSMessage(addr string) (raw []byte, err error) {
queryMsg := &lib.Message{
Header: lib.Header{
// "In order to maximize cache friendliness, DoH clients using media
// formats that include DNS ID, such as application/dns-message, SHOULD
// use a DNS ID of 0 in every DNS request."
// (see: section 4.1)
ID: 0,
QR: 0,
Opcode: lib.OpcodeQuery,
QDCOUNT: 1,
RD: 1,
},
Questions: []*lib.Question{
{
QNAME: addr,
QTYPE: lib.QTypeA,
QCLASS: lib.QClassIN,
},
},
}
raw, err = queryMsg.Marshal()
return
}