2018-11-01 11:45:32 +00:00
|
|
|
package upstream
|
|
|
|
|
|
|
|
import (
|
2018-11-05 17:40:10 +00:00
|
|
|
"crypto/tls"
|
2018-11-05 20:52:11 +00:00
|
|
|
"time"
|
|
|
|
|
2018-11-01 11:45:32 +00:00
|
|
|
"github.com/miekg/dns"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
)
|
|
|
|
|
|
|
|
// DnsUpstream is a very simple upstream implementation for plain DNS
|
|
|
|
type DnsUpstream struct {
|
2018-11-05 17:40:10 +00:00
|
|
|
endpoint string // IP:port
|
|
|
|
timeout time.Duration // Max read and write timeout
|
|
|
|
proto string // Protocol (tcp, tcp-tls, or udp)
|
|
|
|
transport *Transport // Persistent connections cache
|
2018-11-01 11:45:32 +00:00
|
|
|
}
|
|
|
|
|
2018-11-05 17:40:10 +00:00
|
|
|
// NewDnsUpstream creates a new DNS upstream
|
|
|
|
func NewDnsUpstream(endpoint string, proto string, tlsServerName string) (Upstream, error) {
|
|
|
|
u := &DnsUpstream{
|
|
|
|
endpoint: endpoint,
|
|
|
|
timeout: defaultTimeout,
|
|
|
|
proto: proto,
|
|
|
|
}
|
|
|
|
|
|
|
|
var tlsConfig *tls.Config
|
|
|
|
|
2018-11-05 22:14:28 +00:00
|
|
|
if proto == "tcp-tls" {
|
2018-11-05 17:40:10 +00:00
|
|
|
tlsConfig = new(tls.Config)
|
|
|
|
tlsConfig.ServerName = tlsServerName
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the connections cache
|
|
|
|
u.transport = NewTransport(endpoint)
|
|
|
|
u.transport.tlsConfig = tlsConfig
|
|
|
|
u.transport.Start()
|
|
|
|
|
|
|
|
return u, nil
|
2018-11-01 11:45:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Exchange provides an implementation for the Upstream interface
|
|
|
|
func (u *DnsUpstream) Exchange(ctx context.Context, query *dns.Msg) (*dns.Msg, error) {
|
2018-11-05 20:49:31 +00:00
|
|
|
resp, err := u.exchange(u.proto, query)
|
|
|
|
|
|
|
|
// Retry over TCP if response is truncated
|
|
|
|
if err == dns.ErrTruncated && u.proto == "udp" {
|
|
|
|
resp, err = u.exchange("tcp", query)
|
|
|
|
} else if err == dns.ErrTruncated && resp != nil {
|
|
|
|
// Reassemble something to be sent to client
|
|
|
|
m := new(dns.Msg)
|
|
|
|
m.SetReply(query)
|
|
|
|
m.Truncated = true
|
|
|
|
m.Authoritative = true
|
|
|
|
m.Rcode = dns.RcodeSuccess
|
|
|
|
return m, nil
|
|
|
|
}
|
2018-11-01 11:45:32 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
resp = &dns.Msg{}
|
|
|
|
resp.SetRcode(resp, dns.RcodeServerFailure)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, err
|
|
|
|
}
|
2018-11-05 17:40:10 +00:00
|
|
|
|
|
|
|
// Clear resources
|
|
|
|
func (u *DnsUpstream) Close() error {
|
|
|
|
// Close active connections
|
|
|
|
u.transport.Stop()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Performs a synchronous query. It sends the message m via the conn
|
|
|
|
// c and waits for a reply. The conn c is not closed.
|
2018-11-05 20:49:31 +00:00
|
|
|
func (u *DnsUpstream) exchange(proto string, query *dns.Msg) (r *dns.Msg, err error) {
|
2018-11-05 17:40:10 +00:00
|
|
|
// Establish a connection if needed (or reuse cached)
|
2018-11-05 20:49:31 +00:00
|
|
|
conn, err := u.transport.Dial(proto)
|
2018-11-05 17:40:10 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the request with a timeout
|
|
|
|
conn.SetWriteDeadline(time.Now().Add(u.timeout))
|
|
|
|
if err = conn.WriteMsg(query); err != nil {
|
|
|
|
conn.Close() // Not giving it back
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write response with a timeout
|
|
|
|
conn.SetReadDeadline(time.Now().Add(u.timeout))
|
|
|
|
r, err = conn.ReadMsg()
|
|
|
|
if err != nil {
|
|
|
|
conn.Close() // Not giving it back
|
|
|
|
} else if err == nil && r.Id != query.Id {
|
|
|
|
err = dns.ErrId
|
|
|
|
conn.Close() // Not giving it back
|
|
|
|
}
|
|
|
|
|
2018-11-05 22:14:28 +00:00
|
|
|
if err == nil {
|
|
|
|
// Return it back to the connections cache if there were no errors
|
|
|
|
u.transport.Yield(conn)
|
|
|
|
}
|
2018-11-05 17:40:10 +00:00
|
|
|
return r, err
|
|
|
|
}
|