-
Notifications
You must be signed in to change notification settings - Fork 12
/
upstream.go
224 lines (189 loc) · 5.57 KB
/
upstream.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package main
import (
"bytes"
"crypto/tls"
"encoding/base64"
"github.com/miekg/dns"
"go.uber.org/zap"
"golang.org/x/net/http2"
"io/ioutil"
"net/http"
"net/url"
"time"
)
// Upstream describes an upstream interface
type Upstream interface {
Type() string
Name() string
Query(w dns.ResponseWriter, req *dns.Msg)
}
// UpstreamImpl describes an abstract implement of the Upstream interface
type UpstreamImpl struct {
name string
address string
}
// Name returns the upstream name
func (u *UpstreamImpl) Name() string {
return u.name
}
// Address returns the upstream address
func (u *UpstreamImpl) Address() string {
return u.address
}
// UpstreamDNS is a DNS upstream
type UpstreamDNS struct {
UpstreamImpl
}
// UpstreamDoh is an abstract DNS-over-HTTPS upstream
type UpstreamDoh struct {
UpstreamImpl
proxy *url.URL
}
// UpstreamDohGet is the DNS-over-HTTPS upstream implement using HTTP GET method
type UpstreamDohGet struct {
UpstreamDoh
}
// UpstreamDohPost is the DNS-over-HTTPS upstream implement using HTTP POST method
type UpstreamDohPost struct {
UpstreamDoh
}
// UpstreamBlackHole does nothing to all DNS requests
type UpstreamBlackHole struct{}
// UpstreamReject returns error the all DNS requests
type UpstreamReject struct{}
// Type returns the type of the dns upstream
func (upstream *UpstreamDNS) Type() string {
return "dns"
}
// Type returns the type of the DNS-over-HTTPS upstream using HTTP GET method
func (upstream *UpstreamDohGet) Type() string {
return "doh-get"
}
// Type returns the type of the DNS-over-HTTPS upstream using HTTP POST method
func (upstream *UpstreamDohPost) Type() string {
return "doh-post"
}
// Type returns the type of the black hole upstream
func (upstream *UpstreamBlackHole) Type() string {
return "blackhole"
}
// Type returns the type of the reject upstream
func (upstream *UpstreamReject) Type() string {
return "reject"
}
// Name returns the black hole upstream name
func (upstream *UpstreamBlackHole) Name() string {
return "blackhole"
}
// Name returns the reject upstream name
func (upstream *UpstreamReject) Name() string {
return "reject"
}
func (upstream *UpstreamDoh) dohQuery(w dns.ResponseWriter, req *dns.Msg, method string) {
logger := zap.L().Named("answer").With(zap.Uint16("id", req.Id))
u, err := url.Parse(upstream.address)
if err != nil {
logger.Fatal("doh url parse error", zap.Error(err))
}
// Update TLS and HTTP client configuration
tlsConfig := &tls.Config{ServerName: u.Hostname()}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
DisableCompression: true,
MaxIdleConns: 1,
}
if upstream.proxy != nil {
transport.Proxy = http.ProxyURL(upstream.proxy)
}
http2.ConfigureTransport(transport)
client := &http.Client{
Timeout: 5 * time.Second,
Transport: transport,
}
// start
msg, err := req.Pack()
if err != nil {
logger.Error("doh pack message error", zap.Error(err))
return
}
var httpReq *http.Request
switch method {
case "GET":
base64str := base64.RawURLEncoding.EncodeToString(msg)
httpReq, err = http.NewRequest("GET", u.String()+"?dns="+base64str, bytes.NewBuffer(msg))
case "POST":
httpReq, err = http.NewRequest("POST", u.String(), bytes.NewBuffer(msg))
default:
zap.L().Fatal("illegal http method", zap.String("method", method))
}
if err != nil {
logger.Error("doh req err", zap.Error(err))
return
}
httpReq.Header.Add("Content-Type", "application/dns-message")
httpReq.Header.Set("Connection", "close")
httpReq.Host = u.Hostname()
httpResp, err := client.Do(httpReq)
if err != nil {
logger.Warn("doh resp err", zap.Error(err))
return
}
defer httpResp.Body.Close()
switch httpResp.StatusCode {
case http.StatusOK: // 200
buf, err := ioutil.ReadAll(httpResp.Body)
if err != nil {
logger.Error("doh read http body error", zap.Error(err))
return
}
w.Write(buf)
// set cache
replMsg := &dns.Msg{}
err = replMsg.Unpack(buf)
if err == nil {
SetCache(req.Question[0].String(), replMsg)
}
case http.StatusBadRequest: // 400
logger.Info("DNS query not specified or too small.")
case http.StatusRequestEntityTooLarge: // 413
logger.Info("DNS query is larger than maximum allowed DNS message size.")
case http.StatusUnsupportedMediaType: // 415
logger.Info("Unsupported content type.")
case http.StatusGatewayTimeout: // 504
logger.Info("Resolver timeout while waiting for the query response.")
default:
logger.Info("Unknown http status code", zap.Int("code", httpResp.StatusCode))
}
}
// Query does the exact query action of an DNS upstream
func (upstream *UpstreamDNS) Query(w dns.ResponseWriter, req *dns.Msg) {
c := new(dns.Client)
r, _, err := c.Exchange(req, upstream.address)
if err != nil {
zap.L().Named("answer").Warn("exchange dns server failed",
zap.Uint16("id", req.Id),
)
return
}
w.WriteMsg(r)
// set cache
SetCache(req.Question[0].String(), r)
}
// Query does the exact query action of an DNS-over-HTTPS upstream using HTTP GET method
func (upstream *UpstreamDohGet) Query(w dns.ResponseWriter, req *dns.Msg) {
upstream.dohQuery(w, req, "GET")
}
// Query does the exact query action of an DNS-over-HTTPS upstream using HTTP POST method
func (upstream *UpstreamDohPost) Query(w dns.ResponseWriter, req *dns.Msg) {
upstream.dohQuery(w, req, "POST")
}
// Query does the exact query action of an black hole upstream
func (upstream *UpstreamBlackHole) Query(w dns.ResponseWriter, req *dns.Msg) {
// just do nothing
}
// Query does the exact query action of an reject upstream
func (upstream *UpstreamReject) Query(w dns.ResponseWriter, req *dns.Msg) {
msg := &dns.Msg{}
msg.SetReply(req)
w.WriteMsg(msg)
}