-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathdoh-json.go
123 lines (111 loc) · 3.25 KB
/
doh-json.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
/*
* Code taken from github.com/m13253/dns-over-https/doh-client/google.go with modification
*/
package dnsredir
import (
"context"
"encoding/json"
"fmt"
"github.com/coredns/coredns/request"
"github.com/m13253/dns-over-https/v2/json-dns"
"github.com/miekg/dns"
"io/ioutil"
"net/http"
"net/url"
"strconv"
)
func (uh *UpstreamHost) jsonDnsExchange(ctx context.Context, state *request.Request, requestContentType string) (*http.Response, error) {
r := state.Req
if r.Response {
return nil, fmt.Errorf("received a response packet")
}
if len(r.Question) != 1 {
return nil, fmt.Errorf("JSON DOH only supported one question per query, got %v", len(r.Question))
}
q := r.Question[0]
if q.Qclass != dns.ClassINET {
var QClass string
if c, ok := dns.ClassToString[q.Qclass]; ok {
QClass = c
} else {
QClass = strconv.FormatUint(uint64(q.Qclass), 10)
}
return nil, fmt.Errorf("only IN question class are supported, got %v", QClass)
}
var QType string
if t, ok := dns.TypeToString[q.Qtype]; ok {
QType = t
} else {
QType = strconv.FormatUint(uint64(q.Qtype), 10)
}
reqURL := fmt.Sprintf("%v?ct=%v&name=%v&type=%v",
uh.Name(), requestContentType, url.QueryEscape(q.Name), url.QueryEscape(QType))
if r.CheckingDisabled {
// Disable DNSSEC validation
reqURL += "&cd=1"
}
if opt := r.IsEdns0(); opt != nil {
if opt.Do() {
reqURL += "&do=1"
}
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", headerAccept)
req.Header.Set("User-Agent", userAgent)
return uh.httpClient.Do(req)
}
func (uh *UpstreamHost) jsonDnsParseResponse(state *request.Request, resp *http.Response, contentType string, requestContentType string) (*dns.Msg, error) {
if resp.StatusCode != http.StatusOK {
if contentType != requestContentType {
return nil, fmt.Errorf("upstream %v error: bad status: %v content type: %v",
uh.Name(), resp.StatusCode, contentType)
}
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var respJSON jsondns.Response
err = json.Unmarshal(body, &respJSON)
if err != nil {
return nil, err
}
if respJSON.Status != dns.RcodeSuccess && respJSON.Comment != "" {
log.Warningf("DNS error when query %q: %v", state.Name(), respJSON.Comment)
}
fixEmptyNames(&respJSON)
var udpSize int
if state.W != nil {
udpSize = state.Size()
} else {
// see: healthcheck.go#UpstreamHost.dohSend()
q := state.Req.Question[0]
if q.Name != "." || q.Qtype != dns.TypeNS {
panic(fmt.Sprintf("Expected query is \"IN NS .\" when state.W is nil, got %v", q))
}
}
if udpSize < dns.MinMsgSize {
udpSize = dns.MinMsgSize
}
reply := jsondns.PrepareReply(state.Req)
reply = jsondns.Unmarshal(reply, &respJSON, uint16(udpSize), 0)
return reply, nil
}
// [#2] Fix DNS response empty []RR.Name in DOH JSON API
// Additional section won't be rectified
// see: https://stackoverflow.com/questions/52136176/what-is-additional-section-in-dns-and-how-it-works
func fixEmptyNames(respJSON *jsondns.Response) {
for i := range respJSON.Answer {
if respJSON.Answer[i].Name == "" {
respJSON.Answer[i].Name = "."
}
}
for i := range respJSON.Authority {
if respJSON.Authority[i].Name == "" {
respJSON.Authority[i].Name = "."
}
}
}