2020-01-12 18:23:35 +01:00
|
|
|
package resolver
|
|
|
|
|
|
|
|
import (
|
2020-02-17 22:06:10 +01:00
|
|
|
"bytes"
|
|
|
|
"errors"
|
2020-01-12 18:23:35 +01:00
|
|
|
"fmt"
|
2020-02-17 22:06:10 +01:00
|
|
|
"io/ioutil"
|
2020-01-12 18:23:35 +01:00
|
|
|
"net"
|
2020-02-17 22:06:10 +01:00
|
|
|
"net/http"
|
2020-01-12 18:23:35 +01:00
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
2021-08-25 22:06:34 +02:00
|
|
|
"github.com/0xERR0R/blocky/config"
|
|
|
|
"github.com/0xERR0R/blocky/util"
|
|
|
|
|
2020-01-12 18:23:35 +01:00
|
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
2020-02-17 22:06:10 +01:00
|
|
|
const (
|
|
|
|
defaultTimeout = 2 * time.Second
|
|
|
|
dnsContentType = "application/dns-message"
|
|
|
|
)
|
|
|
|
|
2020-01-12 18:23:35 +01:00
|
|
|
// UpstreamResolver sends request to external DNS server
|
|
|
|
type UpstreamResolver struct {
|
|
|
|
NextResolver
|
2020-02-17 22:06:10 +01:00
|
|
|
upstreamURL string
|
|
|
|
upstreamClient upstreamClient
|
2020-06-21 22:37:50 +02:00
|
|
|
net string
|
2020-02-17 22:06:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type upstreamClient interface {
|
2020-06-21 22:37:50 +02:00
|
|
|
callExternal(msg *dns.Msg, upstreamURL string,
|
|
|
|
protocol RequestProtocol) (response *dns.Msg, rtt time.Duration, err error)
|
2020-02-17 22:06:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type dnsUpstreamClient struct {
|
2020-06-21 22:37:50 +02:00
|
|
|
tcpClient, udpClient *dns.Client
|
2020-02-17 22:06:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type httpUpstreamClient struct {
|
|
|
|
client *http.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
func createUpstreamClient(cfg config.Upstream) (client upstreamClient, upstreamURL string) {
|
2020-06-21 22:37:50 +02:00
|
|
|
if cfg.Net == config.NetHTTPS {
|
2020-02-17 22:06:10 +01:00
|
|
|
return &httpUpstreamClient{
|
|
|
|
client: &http.Client{
|
2021-08-21 23:19:30 +02:00
|
|
|
Transport: &http.Transport{
|
|
|
|
Dial: (util.Dialer(config.GetConfig())).Dial,
|
|
|
|
TLSHandshakeTimeout: 5 * time.Second,
|
|
|
|
},
|
2020-02-17 22:06:10 +01:00
|
|
|
Timeout: defaultTimeout,
|
|
|
|
},
|
|
|
|
}, fmt.Sprintf("%s://%s:%d%s", cfg.Net, cfg.Host, cfg.Port, cfg.Path)
|
|
|
|
}
|
|
|
|
|
2020-06-21 22:37:50 +02:00
|
|
|
if cfg.Net == config.NetTCPTLS {
|
|
|
|
return &dnsUpstreamClient{
|
|
|
|
tcpClient: &dns.Client{
|
|
|
|
Net: cfg.Net,
|
|
|
|
Timeout: defaultTimeout,
|
2021-08-21 23:19:30 +02:00
|
|
|
Dialer: util.Dialer(config.GetConfig()),
|
2020-06-21 22:37:50 +02:00
|
|
|
},
|
|
|
|
}, net.JoinHostPort(cfg.Host, strconv.Itoa(int(cfg.Port)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// tcp+udp
|
2020-02-17 22:06:10 +01:00
|
|
|
return &dnsUpstreamClient{
|
2020-06-21 22:37:50 +02:00
|
|
|
tcpClient: &dns.Client{
|
|
|
|
Net: "tcp",
|
|
|
|
Timeout: defaultTimeout,
|
2021-08-21 23:19:30 +02:00
|
|
|
Dialer: util.Dialer(config.GetConfig()),
|
2020-06-21 22:37:50 +02:00
|
|
|
},
|
|
|
|
udpClient: &dns.Client{
|
|
|
|
Net: "udp",
|
2020-02-17 22:06:10 +01:00
|
|
|
Timeout: defaultTimeout,
|
2021-08-21 23:19:30 +02:00
|
|
|
Dialer: util.Dialer(config.GetConfig()),
|
2020-02-17 22:06:10 +01:00
|
|
|
},
|
|
|
|
}, net.JoinHostPort(cfg.Host, strconv.Itoa(int(cfg.Port)))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *httpUpstreamClient) callExternal(msg *dns.Msg,
|
2021-03-05 22:52:22 +01:00
|
|
|
upstreamURL string, _ RequestProtocol) (*dns.Msg, time.Duration, error) {
|
2020-02-17 22:06:10 +01:00
|
|
|
start := time.Now()
|
|
|
|
|
|
|
|
rawDNSMessage, err := msg.Pack()
|
|
|
|
|
|
|
|
if err != nil {
|
2021-03-05 22:52:22 +01:00
|
|
|
return nil, 0, fmt.Errorf("can't pack message: %w", err)
|
2020-02-17 22:06:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
httpResponse, err := r.client.Post(upstreamURL, dnsContentType, bytes.NewReader(rawDNSMessage))
|
|
|
|
|
|
|
|
if err != nil {
|
2021-03-05 22:52:22 +01:00
|
|
|
return nil, 0, fmt.Errorf("can't perform https request: %w", err)
|
2020-02-17 22:06:10 +01:00
|
|
|
}
|
2021-01-19 21:52:24 +01:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
util.LogOnError("cant close response body ", httpResponse.Body.Close())
|
|
|
|
}()
|
2020-02-17 22:06:10 +01:00
|
|
|
|
|
|
|
if httpResponse.StatusCode != http.StatusOK {
|
|
|
|
return nil, 0, fmt.Errorf("http return code should be %d, but received %d", http.StatusOK, httpResponse.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
contentType := httpResponse.Header.Get("content-type")
|
|
|
|
if contentType != dnsContentType {
|
|
|
|
return nil, 0, fmt.Errorf("http return content type should be '%s', but was '%s'",
|
|
|
|
dnsContentType, contentType)
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(httpResponse.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, errors.New("can't read response body")
|
|
|
|
}
|
|
|
|
|
|
|
|
response := dns.Msg{}
|
|
|
|
err = response.Unpack(body)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, errors.New("can't unpack message")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &response, time.Since(start), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *dnsUpstreamClient) callExternal(msg *dns.Msg,
|
2020-06-21 22:37:50 +02:00
|
|
|
upstreamURL string, protocol RequestProtocol) (response *dns.Msg, rtt time.Duration, err error) {
|
|
|
|
if protocol == TCP {
|
|
|
|
response, rtt, err = r.tcpClient.Exchange(msg, upstreamURL)
|
|
|
|
if err != nil {
|
|
|
|
// try UDP as fallback
|
2021-03-05 22:52:22 +01:00
|
|
|
var opErr *net.OpError
|
|
|
|
if errors.As(err, &opErr) {
|
2021-03-14 22:11:01 +01:00
|
|
|
if opErr.Op == "dial" && r.udpClient != nil {
|
2020-06-21 22:37:50 +02:00
|
|
|
return r.udpClient.Exchange(msg, upstreamURL)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return response, rtt, err
|
|
|
|
}
|
|
|
|
|
2020-07-08 21:55:47 +02:00
|
|
|
if r.udpClient != nil {
|
|
|
|
return r.udpClient.Exchange(msg, upstreamURL)
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.tcpClient.Exchange(msg, upstreamURL)
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2021-02-26 13:45:57 +01:00
|
|
|
// NewUpstreamResolver creates new resolver instance
|
2020-06-21 22:37:50 +02:00
|
|
|
func NewUpstreamResolver(upstream config.Upstream) *UpstreamResolver {
|
2020-02-17 22:06:10 +01:00
|
|
|
upstreamClient, upstreamURL := createUpstreamClient(upstream)
|
2020-01-12 18:23:35 +01:00
|
|
|
|
|
|
|
return &UpstreamResolver{
|
2020-02-17 22:06:10 +01:00
|
|
|
upstreamClient: upstreamClient,
|
2020-06-21 22:37:50 +02:00
|
|
|
upstreamURL: upstreamURL,
|
|
|
|
net: upstream.Net}
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2021-02-26 13:45:57 +01:00
|
|
|
// Configuration return current resolver configuration
|
2020-01-12 18:23:35 +01:00
|
|
|
func (r *UpstreamResolver) Configuration() (result []string) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-03-06 23:00:14 +01:00
|
|
|
func (r UpstreamResolver) String() string {
|
2020-06-21 22:37:50 +02:00
|
|
|
return fmt.Sprintf("upstream '%s:%s'", r.net, r.upstreamURL)
|
2020-03-06 23:00:14 +01:00
|
|
|
}
|
|
|
|
|
2021-02-26 13:45:57 +01:00
|
|
|
// Resolve calls external resolver
|
2020-01-12 18:23:35 +01:00
|
|
|
func (r *UpstreamResolver) Resolve(request *Request) (response *Response, err error) {
|
|
|
|
logger := withPrefix(request.Log, "upstream_resolver")
|
|
|
|
|
|
|
|
attempt := 1
|
|
|
|
|
|
|
|
var rtt time.Duration
|
|
|
|
|
|
|
|
var resp *dns.Msg
|
|
|
|
|
|
|
|
for attempt <= 3 {
|
2020-06-21 22:37:50 +02:00
|
|
|
if resp, rtt, err = r.upstreamClient.callExternal(request.Req, r.upstreamURL, request.Protocol); err == nil {
|
2020-01-12 18:23:35 +01:00
|
|
|
logger.WithFields(logrus.Fields{
|
|
|
|
"answer": util.AnswerToString(resp.Answer),
|
|
|
|
"return_code": dns.RcodeToString[resp.Rcode],
|
2020-02-17 22:06:10 +01:00
|
|
|
"upstream": r.upstreamURL,
|
2020-06-21 22:37:50 +02:00
|
|
|
"protocol": request.Protocol,
|
|
|
|
"net": r.net,
|
2020-01-12 18:23:35 +01:00
|
|
|
"response_time_ms": rtt.Milliseconds(),
|
|
|
|
}).Debugf("received response from upstream")
|
|
|
|
|
2021-04-06 21:34:10 +02:00
|
|
|
return &Response{Res: resp, Reason: fmt.Sprintf("RESOLVED (%s)", r.upstreamURL)}, nil
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|
|
|
|
|
2021-03-05 22:52:22 +01:00
|
|
|
var netErr net.Error
|
|
|
|
if errors.As(err, &netErr) && (netErr.Timeout() || netErr.Temporary()) {
|
2020-01-12 18:23:35 +01:00
|
|
|
logger.WithField("attempt", attempt).Debugf("Temporary network error / Timeout occurred, retrying...")
|
|
|
|
attempt++
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-21 23:07:24 +02:00
|
|
|
return response, err
|
2020-01-12 18:23:35 +01:00
|
|
|
}
|