ARES-899: Fixes DoH client as system resolver. Fixes #91
This commit is contained in:
parent
6d63f84a75
commit
f18209af7d
|
@ -138,6 +138,12 @@ func Commands() []*cli.Command {
|
||||||
Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"),
|
Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"),
|
||||||
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
|
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
|
||||||
},
|
},
|
||||||
|
&cli.StringSliceFlag{
|
||||||
|
Name: "bootstrap",
|
||||||
|
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
|
||||||
|
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
|
||||||
|
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
ArgsUsage: " ", // can't be the empty string or we get the default output
|
ArgsUsage: " ", // can't be the empty string or we get the default output
|
||||||
Hidden: false,
|
Hidden: false,
|
||||||
|
@ -963,6 +969,13 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||||
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
|
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
|
||||||
Hidden: shouldHide,
|
Hidden: shouldHide,
|
||||||
}),
|
}),
|
||||||
|
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
|
||||||
|
Name: "proxy-dns-bootstrap",
|
||||||
|
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
|
||||||
|
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
|
||||||
|
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
|
||||||
|
Hidden: shouldHide,
|
||||||
|
}),
|
||||||
altsrc.NewDurationFlag(&cli.DurationFlag{
|
altsrc.NewDurationFlag(&cli.DurationFlag{
|
||||||
Name: "grace-period",
|
Name: "grace-period",
|
||||||
Usage: "Duration to accept new requests after cloudflared receives first SIGINT/SIGTERM. A second SIGINT/SIGTERM will force cloudflared to shutdown immediately.",
|
Usage: "Duration to accept new requests after cloudflared receives first SIGINT/SIGTERM. A second SIGINT/SIGTERM will force cloudflared to shutdown immediately.",
|
||||||
|
|
|
@ -14,7 +14,7 @@ func runDNSProxyServer(c *cli.Context, dnsReadySignal, shutdownC chan struct{})
|
||||||
logger.Errorf("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
|
logger.Errorf("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
|
||||||
return errors.New("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
|
return errors.New("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
|
||||||
}
|
}
|
||||||
listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream"))
|
listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream"), c.StringSlice("proxy-dns-bootstrap"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close(dnsReadySignal)
|
close(dnsReadySignal)
|
||||||
listener.Stop()
|
listener.Stop()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,31 +25,16 @@ const (
|
||||||
type UpstreamHTTPS struct {
|
type UpstreamHTTPS struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
endpoint *url.URL
|
endpoint *url.URL
|
||||||
|
bootstraps []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUpstreamHTTPS creates a new DNS over HTTPS upstream from hostname
|
// NewUpstreamHTTPS creates a new DNS over HTTPS upstream from endpoint
|
||||||
func NewUpstreamHTTPS(endpoint string) (Upstream, error) {
|
func NewUpstreamHTTPS(endpoint string, bootstraps []string) (Upstream, error) {
|
||||||
u, err := url.Parse(endpoint)
|
u, err := url.Parse(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return &UpstreamHTTPS{client: configureClient(u.Hostname()), endpoint: u, bootstraps: bootstraps}, nil
|
||||||
// Update TLS and HTTP client configuration
|
|
||||||
tls := &tls.Config{ServerName: u.Hostname()}
|
|
||||||
transport := &http.Transport{
|
|
||||||
TLSClientConfig: tls,
|
|
||||||
DisableCompression: true,
|
|
||||||
MaxIdleConns: 1,
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
}
|
|
||||||
http2.ConfigureTransport(transport)
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Timeout: defaultTimeout,
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &UpstreamHTTPS{client: client, endpoint: u}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange provides an implementation for the Upstream interface
|
// Exchange provides an implementation for the Upstream interface
|
||||||
|
@ -58,34 +44,55 @@ func (u *UpstreamHTTPS) Exchange(ctx context.Context, query *dns.Msg) (*dns.Msg,
|
||||||
return nil, errors.Wrap(err, "failed to pack DNS query")
|
return nil, errors.Wrap(err, "failed to pack DNS query")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(query.Question) > 0 && query.Question[0].Name == fmt.Sprintf("%s.", u.endpoint.Hostname()) {
|
||||||
|
for _, bootstrap := range u.bootstraps {
|
||||||
|
endpoint, client, err := configureBootstrap(bootstrap)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("failed to configure boostrap upstream %s", bootstrap)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg, err := exchange(queryBuf, query.Id, endpoint, client)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Errorf("failed to connect to a boostrap upstream %s", bootstrap)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to reach any bootstrap upstream: %v", u.bootstraps)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exchange(queryBuf, query.Id, u.endpoint, u.client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exchange(msg []byte, queryID uint16, endpoint *url.URL, client *http.Client) (*dns.Msg, error) {
|
||||||
// No content negotiation for now, use DNS wire format
|
// No content negotiation for now, use DNS wire format
|
||||||
buf, backendErr := u.exchangeWireformat(queryBuf)
|
buf, backendErr := exchangeWireformat(msg, endpoint, client)
|
||||||
if backendErr == nil {
|
if backendErr == nil {
|
||||||
response := &dns.Msg{}
|
response := &dns.Msg{}
|
||||||
if err := response.Unpack(buf); err != nil {
|
if err := response.Unpack(buf); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unpack DNS response from body")
|
return nil, errors.Wrap(err, "failed to unpack DNS response from body")
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Id = query.Id
|
response.Id = queryID
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithError(backendErr).Errorf("failed to connect to an HTTPS backend %q", u.endpoint)
|
log.WithError(backendErr).Errorf("failed to connect to an HTTPS backend %q", endpoint)
|
||||||
return nil, backendErr
|
return nil, backendErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform message exchange with the default UDP wireformat defined in current draft
|
// Perform message exchange with the default UDP wireformat defined in current draft
|
||||||
// https://datatracker.ietf.org/doc/draft-ietf-doh-dns-over-https
|
// https://datatracker.ietf.org/doc/draft-ietf-doh-dns-over-https
|
||||||
func (u *UpstreamHTTPS) exchangeWireformat(msg []byte) ([]byte, error) {
|
func exchangeWireformat(msg []byte, endpoint *url.URL, client *http.Client) ([]byte, error) {
|
||||||
req, err := http.NewRequest("POST", u.endpoint.String(), bytes.NewBuffer(msg))
|
req, err := http.NewRequest("POST", endpoint.String(), bytes.NewBuffer(msg))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create an HTTPS request")
|
return nil, errors.Wrap(err, "failed to create an HTTPS request")
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/dns-message")
|
req.Header.Add("Content-Type", "application/dns-message")
|
||||||
req.Host = u.endpoint.Host
|
req.Host = endpoint.Host
|
||||||
|
|
||||||
resp, err := u.client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to perform an HTTPS request")
|
return nil, errors.Wrap(err, "failed to perform an HTTPS request")
|
||||||
}
|
}
|
||||||
|
@ -104,3 +111,33 @@ func (u *UpstreamHTTPS) exchangeWireformat(msg []byte) ([]byte, error) {
|
||||||
|
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configureBootstrap(bootstrap string) (*url.URL, *http.Client, error) {
|
||||||
|
b, err := url.Parse(bootstrap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(b.Hostname()); ip == nil {
|
||||||
|
return nil, nil, fmt.Errorf("bootstrap address of %s must be an IP address", b.Hostname())
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, configureClient(b.Hostname()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// configureClient will configure a HTTPS client for upstream DoH requests
|
||||||
|
func configureClient(hostname string) *http.Client {
|
||||||
|
// Update TLS and HTTP client configuration
|
||||||
|
tls := &tls.Config{ServerName: hostname}
|
||||||
|
transport := &http.Transport{
|
||||||
|
TLSClientConfig: tls,
|
||||||
|
DisableCompression: true,
|
||||||
|
MaxIdleConns: 1,
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
}
|
||||||
|
http2.ConfigureTransport(transport)
|
||||||
|
|
||||||
|
return &http.Client{
|
||||||
|
Timeout: defaultTimeout,
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ func Run(c *cli.Context) error {
|
||||||
|
|
||||||
go metrics.ServeMetrics(metricsListener, nil, logger)
|
go metrics.ServeMetrics(metricsListener, nil, logger)
|
||||||
|
|
||||||
listener, err := CreateListener(c.String("address"), uint16(c.Uint("port")), c.StringSlice("upstream"))
|
listener, err := CreateListener(c.String("address"), uint16(c.Uint("port")), c.StringSlice("upstream"), c.StringSlice("bootstrap"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Errorf("Failed to create the listeners")
|
logger.WithError(err).Errorf("Failed to create the listeners")
|
||||||
return err
|
return err
|
||||||
|
@ -117,12 +117,12 @@ func (l *Listener) Stop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateListener configures the server and bound sockets
|
// CreateListener configures the server and bound sockets
|
||||||
func CreateListener(address string, port uint16, upstreams []string) (*Listener, error) {
|
func CreateListener(address string, port uint16, upstreams []string, bootstraps []string) (*Listener, error) {
|
||||||
// Build the list of upstreams
|
// Build the list of upstreams
|
||||||
upstreamList := make([]Upstream, 0)
|
upstreamList := make([]Upstream, 0)
|
||||||
for _, url := range upstreams {
|
for _, url := range upstreams {
|
||||||
logger.WithField("url", url).Infof("Adding DNS upstream")
|
logger.WithField("url", url).Infof("Adding DNS upstream")
|
||||||
upstream, err := NewUpstreamHTTPS(url)
|
upstream, err := NewUpstreamHTTPS(url, bootstraps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create HTTPS upstream")
|
return nil, errors.Wrap(err, "failed to create HTTPS upstream")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue