mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-07-27 07:59:58 +00:00
ARES-899: Fixes DoH client as system resolver. Fixes #91
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
@@ -22,33 +23,18 @@ const (
|
||||
|
||||
// UpstreamHTTPS is the upstream implementation for DNS over HTTPS service
|
||||
type UpstreamHTTPS struct {
|
||||
client *http.Client
|
||||
endpoint *url.URL
|
||||
client *http.Client
|
||||
endpoint *url.URL
|
||||
bootstraps []string
|
||||
}
|
||||
|
||||
// NewUpstreamHTTPS creates a new DNS over HTTPS upstream from hostname
|
||||
func NewUpstreamHTTPS(endpoint string) (Upstream, error) {
|
||||
// NewUpstreamHTTPS creates a new DNS over HTTPS upstream from endpoint
|
||||
func NewUpstreamHTTPS(endpoint string, bootstraps []string) (Upstream, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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
|
||||
return &UpstreamHTTPS{client: configureClient(u.Hostname()), endpoint: u, bootstraps: bootstraps}, nil
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
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
|
||||
buf, backendErr := u.exchangeWireformat(queryBuf)
|
||||
buf, backendErr := exchangeWireformat(msg, endpoint, client)
|
||||
if backendErr == nil {
|
||||
response := &dns.Msg{}
|
||||
if err := response.Unpack(buf); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unpack DNS response from body")
|
||||
}
|
||||
|
||||
response.Id = query.Id
|
||||
response.Id = queryID
|
||||
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
|
||||
}
|
||||
|
||||
// Perform message exchange with the default UDP wireformat defined in current draft
|
||||
// https://datatracker.ietf.org/doc/draft-ietf-doh-dns-over-https
|
||||
func (u *UpstreamHTTPS) exchangeWireformat(msg []byte) ([]byte, error) {
|
||||
req, err := http.NewRequest("POST", u.endpoint.String(), bytes.NewBuffer(msg))
|
||||
func exchangeWireformat(msg []byte, endpoint *url.URL, client *http.Client) ([]byte, error) {
|
||||
req, err := http.NewRequest("POST", endpoint.String(), bytes.NewBuffer(msg))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create an HTTPS request")
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user