mirror of
https://github.com/m13253/dns-over-https.git
synced 2026-10-23 10:52:40 +00:00
Merge pull request #58 from gdm85/master
Add support for DNS-over-TLS upstream resolvers
This commit is contained in:
6 files changed
+97
-40
No files matched your search
+11
-8
@@ -36,7 +36,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/m13253/dns-over-https/doh-client/selector"
|
"github.com/m13253/dns-over-https/doh-client/selector"
|
||||||
"github.com/m13253/dns-over-https/json-dns"
|
jsonDNS "github.com/m13253/dns-over-https/json-dns"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
|
|||||||
|
|
||||||
body, err := ioutil.ReadAll(req.response.Body)
|
body, err := ioutil.ReadAll(req.response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("read error from upstream %s: %v\n", req.currentUpstream, err)
|
||||||
req.reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(req.reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
@@ -191,7 +191,7 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
|
|||||||
if nowDate, err := time.Parse(http.TimeFormat, headerNow); err == nil {
|
if nowDate, err := time.Parse(http.TimeFormat, headerNow); err == nil {
|
||||||
now = nowDate
|
now = nowDate
|
||||||
} else {
|
} else {
|
||||||
log.Println(err)
|
log.Printf("Date header parse error from upstream %s: %v\n", req.currentUpstream, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headerLastModified := req.response.Header.Get("Last-Modified")
|
headerLastModified := req.response.Header.Get("Last-Modified")
|
||||||
@@ -200,7 +200,7 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
|
|||||||
if lastModifiedDate, err := time.Parse(http.TimeFormat, headerLastModified); err == nil {
|
if lastModifiedDate, err := time.Parse(http.TimeFormat, headerLastModified); err == nil {
|
||||||
lastModified = lastModifiedDate
|
lastModified = lastModifiedDate
|
||||||
} else {
|
} else {
|
||||||
log.Println(err)
|
log.Printf("Last-Modified header parse error from upstream %s: %v\n", req.currentUpstream, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timeDelta := now.Sub(lastModified)
|
timeDelta := now.Sub(lastModified)
|
||||||
@@ -211,7 +211,7 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
|
|||||||
fullReply := new(dns.Msg)
|
fullReply := new(dns.Msg)
|
||||||
err = fullReply.Unpack(body)
|
err = fullReply.Unpack(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("unpacking error from upstream %s: %v\n", req.currentUpstream, err)
|
||||||
req.reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(req.reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
@@ -233,7 +233,7 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
|
|||||||
|
|
||||||
buf, err := fullReply.Pack()
|
buf, err := fullReply.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("packing error with upstream %s: %v\n", req.currentUpstream, err)
|
||||||
req.reply.Rcode = dns.RcodeServerFailure
|
req.reply.Rcode = dns.RcodeServerFailure
|
||||||
w.WriteMsg(req.reply)
|
w.WriteMsg(req.reply)
|
||||||
return
|
return
|
||||||
@@ -242,12 +242,15 @@ func (c *Client) parseResponseIETF(ctx context.Context, w dns.ResponseWriter, r
|
|||||||
fullReply.Truncated = true
|
fullReply.Truncated = true
|
||||||
buf, err = fullReply.Pack()
|
buf, err = fullReply.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("re-packing error with upstream %s: %v\n", req.currentUpstream, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buf = buf[:req.udpSize]
|
buf = buf[:req.udpSize]
|
||||||
}
|
}
|
||||||
w.Write(buf)
|
_, err = w.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to write to client: %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixRecordTTL(rr dns.RR, delta time.Duration) dns.RR {
|
func fixRecordTTL(rr dns.RR, delta time.Duration) dns.RR {
|
||||||
|
|||||||
+29
-2
@@ -25,6 +25,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
)
|
)
|
||||||
@@ -38,7 +39,6 @@ type config struct {
|
|||||||
Upstream []string `toml:"upstream"`
|
Upstream []string `toml:"upstream"`
|
||||||
Timeout uint `toml:"timeout"`
|
Timeout uint `toml:"timeout"`
|
||||||
Tries uint `toml:"tries"`
|
Tries uint `toml:"tries"`
|
||||||
TCPOnly bool `toml:"tcp_only"`
|
|
||||||
Verbose bool `toml:"verbose"`
|
Verbose bool `toml:"verbose"`
|
||||||
DebugHTTPHeaders []string `toml:"debug_http_headers"`
|
DebugHTTPHeaders []string `toml:"debug_http_headers"`
|
||||||
LogGuessedIP bool `toml:"log_guessed_client_ip"`
|
LogGuessedIP bool `toml:"log_guessed_client_ip"`
|
||||||
@@ -62,7 +62,7 @@ func loadConfig(path string) (*config, error) {
|
|||||||
conf.Path = "/dns-query"
|
conf.Path = "/dns-query"
|
||||||
}
|
}
|
||||||
if len(conf.Upstream) == 0 {
|
if len(conf.Upstream) == 0 {
|
||||||
conf.Upstream = []string{"8.8.8.8:53", "8.8.4.4:53"}
|
conf.Upstream = []string{"udp:8.8.8.8:53", "udp:8.8.4.4:53"}
|
||||||
}
|
}
|
||||||
if conf.Timeout == 0 {
|
if conf.Timeout == 0 {
|
||||||
conf.Timeout = 10
|
conf.Timeout = 10
|
||||||
@@ -75,9 +75,36 @@ func loadConfig(path string) (*config, error) {
|
|||||||
return nil, &configError{"You must specify both -cert and -key to enable TLS"}
|
return nil, &configError{"You must specify both -cert and -key to enable TLS"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate all upstreams
|
||||||
|
for _, us := range conf.Upstream {
|
||||||
|
address, t := addressAndType(us)
|
||||||
|
if address == "" {
|
||||||
|
return nil, &configError{"One of the upstreams has not a (udp|tcp|tcp-tls) prefix e.g. udp:1.1.1.1:53"}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case "tcp", "udp", "tcp-tls":
|
||||||
|
// OK
|
||||||
|
default:
|
||||||
|
return nil, &configError{"Invalid upstream prefix specified, choose one of: udp tcp tcp-tls"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return conf, nil
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rxUpstreamWithTypePrefix = regexp.MustCompile("^[a-z-]+(:)")
|
||||||
|
|
||||||
|
func addressAndType(us string) (string, string) {
|
||||||
|
p := rxUpstreamWithTypePrefix.FindStringSubmatchIndex(us)
|
||||||
|
fmt.Println(p)
|
||||||
|
if len(p) != 4 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return us[p[2]+1:], us[:p[2]]
|
||||||
|
}
|
||||||
|
|
||||||
type configError struct {
|
type configError struct {
|
||||||
err string
|
err string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,16 @@ path = "/dns-query"
|
|||||||
|
|
||||||
# Upstream DNS resolver
|
# Upstream DNS resolver
|
||||||
# If multiple servers are specified, a random one will be chosen each time.
|
# If multiple servers are specified, a random one will be chosen each time.
|
||||||
|
# You can use "udp", "tcp" or "tcp-tls" for the type prefix.
|
||||||
|
# For "udp", UDP will first be used, and switch to TCP when the server asks to
|
||||||
|
# or the response is too large.
|
||||||
|
# For "tcp", only TCP will be used.
|
||||||
|
# For "tcp-tls", DNS-over-TLS (RFC 7858) will be used to secure the upstream connection.
|
||||||
upstream = [
|
upstream = [
|
||||||
"1.1.1.1:53",
|
"udp:1.1.1.1:53",
|
||||||
"1.0.0.1:53",
|
"udp:1.0.0.1:53",
|
||||||
"8.8.8.8:53",
|
"udp:8.8.8.8:53",
|
||||||
"8.8.4.4:53",
|
"udp:8.8.4.4:53",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Upstream timeout
|
# Upstream timeout
|
||||||
@@ -40,9 +45,6 @@ timeout = 10
|
|||||||
# Number of tries if upstream DNS fails
|
# Number of tries if upstream DNS fails
|
||||||
tries = 3
|
tries = 3
|
||||||
|
|
||||||
# Only use TCP for DNS query
|
|
||||||
tcp_only = false
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
verbose = false
|
verbose = false
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -36,7 +36,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/m13253/dns-over-https/json-dns"
|
jsonDNS "github.com/m13253/dns-over-https/json-dns"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter
|
|||||||
req.response.Id = req.transactionID
|
req.response.Id = req.transactionID
|
||||||
respBytes, err := req.response.Pack()
|
respBytes, err := req.response.Pack()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("DNS packet construct failure with upstream %s: %v\n", req.currentUpstream, err)
|
||||||
jsonDNS.FormatError(w, fmt.Sprintf("DNS packet construct failure (%s)", err.Error()), 500)
|
jsonDNS.FormatError(w, fmt.Sprintf("DNS packet construct failure (%s)", err.Error()), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -183,9 +183,13 @@ func (s *Server) generateResponseIETF(ctx context.Context, w http.ResponseWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if respJSON.Status == dns.RcodeServerFailure {
|
if respJSON.Status == dns.RcodeServerFailure {
|
||||||
|
log.Printf("received server failure from upstream %s: %v\n", req.currentUpstream, req.response)
|
||||||
w.WriteHeader(503)
|
w.WriteHeader(503)
|
||||||
}
|
}
|
||||||
w.Write(respBytes)
|
_, err = w.Write(respBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to write to client: %v\n", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
|
// Workaround a bug causing DNSCrypt-Proxy to expect a response with TransactionID = 0xcafe
|
||||||
|
|||||||
+40
-19
@@ -35,15 +35,16 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/handlers"
|
"github.com/gorilla/handlers"
|
||||||
"github.com/m13253/dns-over-https/json-dns"
|
jsonDNS "github.com/m13253/dns-over-https/json-dns"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
conf *config
|
conf *config
|
||||||
udpClient *dns.Client
|
udpClient *dns.Client
|
||||||
tcpClient *dns.Client
|
tcpClient *dns.Client
|
||||||
servemux *http.ServeMux
|
tcpClientTLS *dns.Client
|
||||||
|
servemux *http.ServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRequest struct {
|
type DNSRequest struct {
|
||||||
@@ -69,6 +70,10 @@ func NewServer(conf *config) (*Server, error) {
|
|||||||
Net: "tcp",
|
Net: "tcp",
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
},
|
},
|
||||||
|
tcpClientTLS: &dns.Client{
|
||||||
|
Net: "tcp-tls",
|
||||||
|
Timeout: timeout,
|
||||||
|
},
|
||||||
servemux: http.NewServeMux(),
|
servemux: http.NewServeMux(),
|
||||||
}
|
}
|
||||||
if conf.LocalAddr != "" {
|
if conf.LocalAddr != "" {
|
||||||
@@ -88,6 +93,10 @@ func NewServer(conf *config) (*Server, error) {
|
|||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
LocalAddr: tcpLocalAddr,
|
LocalAddr: tcpLocalAddr,
|
||||||
}
|
}
|
||||||
|
s.tcpClientTLS.Dialer = &net.Dialer{
|
||||||
|
Timeout: timeout,
|
||||||
|
LocalAddr: tcpLocalAddr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.servemux.HandleFunc(conf.Path, s.handlerFunc)
|
s.servemux.HandleFunc(conf.Path, s.handlerFunc)
|
||||||
return s, nil
|
return s, nil
|
||||||
@@ -279,23 +288,35 @@ func (s *Server) doDNSQuery(ctx context.Context, req *DNSRequest) (resp *DNSRequ
|
|||||||
for i := uint(0); i < s.conf.Tries; i++ {
|
for i := uint(0); i < s.conf.Tries; i++ {
|
||||||
req.currentUpstream = s.conf.Upstream[rand.Intn(numServers)]
|
req.currentUpstream = s.conf.Upstream[rand.Intn(numServers)]
|
||||||
|
|
||||||
// Use TCP if always configured to or if the Query type dictates it (AXFR)
|
upstream, t := addressAndType(req.currentUpstream)
|
||||||
if s.conf.TCPOnly || (s.indexQuestionType(req.request, dns.TypeAXFR) > -1) {
|
|
||||||
req.response, _, err = s.tcpClient.Exchange(req.request, req.currentUpstream)
|
|
||||||
} else {
|
|
||||||
req.response, _, err = s.udpClient.Exchange(req.request, req.currentUpstream)
|
|
||||||
if err == nil && req.response != nil && req.response.Truncated {
|
|
||||||
log.Println(err)
|
|
||||||
req.response, _, err = s.tcpClient.Exchange(req.request, req.currentUpstream)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry with TCP if this was an IXFR request and we only received an SOA
|
switch t {
|
||||||
if (s.indexQuestionType(req.request, dns.TypeIXFR) > -1) &&
|
default:
|
||||||
(len(req.response.Answer) == 1) &&
|
log.Printf("invalid DNS type %q in upstream %q", t, upstream)
|
||||||
(req.response.Answer[0].Header().Rrtype == dns.TypeSOA) {
|
return nil, &configError{"invalid DNS type"}
|
||||||
req.response, _, err = s.tcpClient.Exchange(req.request, req.currentUpstream)
|
// Use DNS-over-TLS (DoT) if configured to do so
|
||||||
|
case "tcp-tls":
|
||||||
|
req.response, _, err = s.tcpClientTLS.Exchange(req.request, upstream)
|
||||||
|
case "tcp", "udp":
|
||||||
|
// Use TCP if always configured to or if the Query type dictates it (AXFR)
|
||||||
|
if t == "tcp" || (s.indexQuestionType(req.request, dns.TypeAXFR) > -1) {
|
||||||
|
req.response, _, err = s.tcpClient.Exchange(req.request, upstream)
|
||||||
|
} else {
|
||||||
|
req.response, _, err = s.udpClient.Exchange(req.request, upstream)
|
||||||
|
if err == nil && req.response != nil && req.response.Truncated {
|
||||||
|
log.Println(err)
|
||||||
|
req.response, _, err = s.tcpClient.Exchange(req.request, upstream)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry with TCP if this was an IXFR request and we only received an SOA
|
||||||
|
if (s.indexQuestionType(req.request, dns.TypeIXFR) > -1) &&
|
||||||
|
(len(req.response.Answer) == 1) &&
|
||||||
|
(req.response.Answer[0].Header().Rrtype == dns.TypeSOA) {
|
||||||
|
req.response, _, err = s.tcpClient.Exchange(req.request, upstream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func PrepareReply(req *dns.Msg) *dns.Msg {
|
|||||||
reply := new(dns.Msg)
|
reply := new(dns.Msg)
|
||||||
reply.Id = req.Id
|
reply.Id = req.Id
|
||||||
reply.Response = true
|
reply.Response = true
|
||||||
reply.Opcode = reply.Opcode
|
reply.Opcode = req.Opcode
|
||||||
reply.RecursionDesired = req.RecursionDesired
|
reply.RecursionDesired = req.RecursionDesired
|
||||||
reply.RecursionAvailable = req.RecursionDesired
|
reply.RecursionAvailable = req.RecursionDesired
|
||||||
reply.CheckingDisabled = req.CheckingDisabled
|
reply.CheckingDisabled = req.CheckingDisabled
|
||||||
|
|||||||
Reference in new issue
Block a user