Commit 380aa884 authored by Matt Harden's avatar Matt Harden Committed by Brad Fitzpatrick

net: allow Resolver to use a custom dialer

In some cases it is desirable to customize the way the DNS server is
contacted, for instance to use a specific LocalAddr. While most
operating-system level resolvers do not allow this, we have the
opportunity to do so with the Go resolver. Most of the code was
already in place to allow tests to override the dialer. This exposes
that functionality, and as a side effect eliminates the need for a
testing hook.

Fixes #17404

Change-Id: I1c5e570f8edbcf630090f8ec6feb52e379e3e5c0
Reviewed-on: https://go-review.googlesource.com/37260
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 3b5637ff
......@@ -304,7 +304,7 @@ var pkgDeps = map[string][]string{
// do networking portably, it must have a small dependency set: just L0+basic os.
"net": {
"L0", "CGO",
"context", "math/rand", "os", "sort", "syscall", "time",
"context", "math/rand", "os", "reflect", "sort", "syscall", "time",
"internal/nettrace", "internal/poll",
"internal/syscall/windows", "internal/singleflight", "internal/race",
"golang_org/x/net/lif", "golang_org/x/net/route",
......
......@@ -25,13 +25,6 @@ import (
"time"
)
// A dnsDialer provides dialing suitable for DNS queries.
type dnsDialer interface {
dialDNS(ctx context.Context, network, addr string) (dnsConn, error)
}
var testHookDNSDialer = func() dnsDialer { return &Dialer{} }
// A dnsConn represents a DNS transport endpoint.
type dnsConn interface {
io.Closer
......@@ -116,33 +109,8 @@ func dnsRoundTripTCP(c io.ReadWriter, query *dnsMsg) (*dnsMsg, error) {
return resp, nil
}
func (d *Dialer) dialDNS(ctx context.Context, network, server string) (dnsConn, error) {
switch network {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
default:
return nil, UnknownNetworkError(network)
}
// Calling Dial here is scary -- we have to be sure not to
// dial a name that will require a DNS lookup, or Dial will
// call back here to translate it. The DNS config parser has
// already checked that all the cfg.servers are IP
// addresses, which Dial will use without a DNS lookup.
c, err := d.DialContext(ctx, network, server)
if err != nil {
return nil, mapErr(err)
}
switch network {
case "tcp", "tcp4", "tcp6":
return c.(*TCPConn), nil
case "udp", "udp4", "udp6":
return c.(*UDPConn), nil
}
panic("unreachable")
}
// exchange sends a query on the connection and hopes for a response.
func exchange(ctx context.Context, server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
d := testHookDNSDialer()
func (r *Resolver) exchange(ctx context.Context, server, name string, qtype uint16, timeout time.Duration) (*dnsMsg, error) {
out := dnsMsg{
dnsMsgHdr: dnsMsgHdr{
recursion_desired: true,
......@@ -158,7 +126,7 @@ func exchange(ctx context.Context, server, name string, qtype uint16, timeout ti
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
c, err := d.dialDNS(ctx, network, server)
c, err := r.dial(ctx, network, server)
if err != nil {
return nil, err
}
......@@ -181,7 +149,7 @@ func exchange(ctx context.Context, server, name string, qtype uint16, timeout ti
// Do a lookup for a single name, which must be rooted
// (otherwise answer will not find the answers).
func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, error) {
func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, error) {
var lastErr error
serverOffset := cfg.serverOffset()
sLen := uint32(len(cfg.servers))
......@@ -190,7 +158,7 @@ func tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype uint16)
for j := uint32(0); j < sLen; j++ {
server := cfg.servers[(serverOffset+j)%sLen]
msg, err := exchange(ctx, server, name, qtype, cfg.timeout)
msg, err := r.exchange(ctx, server, name, qtype, cfg.timeout)
if err != nil {
lastErr = &DNSError{
Err: err.Error(),
......@@ -333,7 +301,7 @@ func (r *Resolver) lookup(ctx context.Context, name string, qtype uint16) (cname
conf := resolvConf.dnsConfig
resolvConf.mu.RUnlock()
for _, fqdn := range conf.nameList(name) {
cname, rrs, err = tryOneName(ctx, conf, fqdn, qtype)
cname, rrs, err = r.tryOneName(ctx, conf, fqdn, qtype)
if err == nil {
break
}
......@@ -512,7 +480,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, name string, order
for _, fqdn := range conf.nameList(name) {
for _, qtype := range qtypes {
go func(qtype uint16) {
cname, rrs, err := tryOneName(ctx, conf, fqdn, qtype)
cname, rrs, err := r.tryOneName(ctx, conf, fqdn, qtype)
lane <- racer{cname, rrs, err}
}(qtype)
}
......
This diff is collapsed.
......@@ -107,6 +107,15 @@ type Resolver struct {
// with resolvers that process AAAA queries incorrectly.
StrictErrors bool
// Dial optionally specifies an alternate dialer for use by
// Go's built-in DNS resolver to make TCP and UDP connections
// to DNS services. The provided addr will always be an IP
// address and not a hostname.
// The Conn returned must be a *TCPConn or *UDPConn as
// requested by the network parameter. If nil, the default
// dialer is used.
Dial func(ctx context.Context, network, addr string) (Conn, error)
// TODO(bradfitz): optional interface impl override hook
// TODO(bradfitz): Timeout time.Duration?
}
......
......@@ -8,6 +8,8 @@ package net
import (
"context"
"errors"
"reflect"
"sync"
)
......@@ -51,6 +53,31 @@ func lookupProtocol(_ context.Context, name string) (int, error) {
return lookupProtocolMap(name)
}
func (r *Resolver) dial(ctx context.Context, network, server string) (dnsConn, error) {
// Calling Dial here is scary -- we have to be sure not to
// dial a name that will require a DNS lookup, or Dial will
// call back here to translate it. The DNS config parser has
// already checked that all the cfg.servers are IP
// addresses, which Dial will use without a DNS lookup.
var c Conn
var err error
if r.Dial != nil {
c, err = r.Dial(ctx, network, server)
} else {
var d Dialer
c, err = d.DialContext(ctx, network, server)
}
if err != nil {
return nil, mapErr(err)
}
dc, ok := c.(dnsConn)
if !ok {
c.Close()
return nil, errors.New("net: Resolver.Dial returned unsupported connection type " + reflect.TypeOf(c).String())
}
return dc, nil
}
func (r *Resolver) lookupHost(ctx context.Context, host string) (addrs []string, err error) {
order := systemConf().hostLookupOrder(host)
if !r.PreferGo && order == hostLookupCgo {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment