diff --git a/libnetwork/resolver.go b/libnetwork/resolver.go index ed17a2d9ef..f311d03d3b 100644 --- a/libnetwork/resolver.go +++ b/libnetwork/resolver.go @@ -4,6 +4,8 @@ import ( "fmt" "net" "strings" + "sync" + "time" log "github.com/Sirupsen/logrus" "github.com/docker/libnetwork/iptables" @@ -37,17 +39,24 @@ const ( ptrIPv6domain = ".ip6.arpa." respTTL = 600 maxExtDNS = 3 //max number of external servers to try + extIOTimeout = 3 * time.Second ) +type extDNSEntry struct { + ipStr string + extConn net.Conn + extOnce sync.Once +} + // resolver implements the Resolver interface type resolver struct { - sb *sandbox - extDNS []string - server *dns.Server - conn *net.UDPConn - tcpServer *dns.Server - tcpListen *net.TCPListener - err error + sb *sandbox + extDNSList [maxExtDNS]extDNSEntry + server *dns.Server + conn *net.UDPConn + tcpServer *dns.Server + tcpListen *net.TCPListener + err error } // NewResolver creates a new instance of the Resolver @@ -136,7 +145,13 @@ func (r *resolver) Stop() { } func (r *resolver) SetExtServers(dns []string) { - r.extDNS = dns + l := len(dns) + if l > maxExtDNS { + l = maxExtDNS + } + for i := 0; i < l; i++ { + r.extDNSList[i].ipStr = dns[i] + } } func (r *resolver) NameServer() string { @@ -202,8 +217,9 @@ func (r *resolver) handlePTRQuery(ptr string, query *dns.Msg) (*dns.Msg, error) func (r *resolver) ServeDNS(w dns.ResponseWriter, query *dns.Msg) { var ( - resp *dns.Msg - err error + extConn net.Conn + resp *dns.Msg + err error ) if query == nil || len(query.Question) == 0 { @@ -221,28 +237,65 @@ func (r *resolver) ServeDNS(w dns.ResponseWriter, query *dns.Msg) { return } + proto := w.LocalAddr().Network() if resp == nil { - if len(r.extDNS) == 0 { - return - } - - num := maxExtDNS - if len(r.extDNS) < maxExtDNS { - num = len(r.extDNS) - } - for i := 0; i < num; i++ { - log.Debugf("Querying ext dns %s:%s for %s[%d]", w.LocalAddr().Network(), r.extDNS[i], name, query.Question[0].Qtype) - - c := &dns.Client{Net: w.LocalAddr().Network()} - addr := fmt.Sprintf("%s:%d", r.extDNS[i], 53) - - resp, _, err = c.Exchange(query, addr) - if err == nil { - resp.Compress = true + for i := 0; i < maxExtDNS; i++ { + extDNS := &r.extDNSList[i] + if extDNS.ipStr == "" { break } - log.Errorf("external resolution failed, %s", err) + log.Debugf("Querying ext dns %s:%s for %s[%d]", proto, extDNS.ipStr, name, query.Question[0].Qtype) + + extConnect := func() { + addr := fmt.Sprintf("%s:%d", extDNS.ipStr, 53) + extConn, err = net.DialTimeout(proto, addr, extIOTimeout) + } + + // For udp clients connection is persisted to reuse for further queries. + // Accessing extDNS.extConn be a race here between go rouines. Hence the + // connection setup is done in a Once block and fetch the extConn again + extConn = extDNS.extConn + if extConn == nil || proto == "tcp" { + if proto == "udp" { + extDNS.extOnce.Do(func() { + r.sb.execFunc(extConnect) + extDNS.extConn = extConn + }) + extConn = extDNS.extConn + } else { + r.sb.execFunc(extConnect) + } + if err != nil { + log.Debugf("Connect failed, %s", err) + continue + } + } + + // Timeout has to be set for every IO operation. + extConn.SetDeadline(time.Now().Add(extIOTimeout)) + co := &dns.Conn{Conn: extConn} + + defer func() { + if proto == "tcp" { + co.Close() + } + }() + err = co.WriteMsg(query) + if err != nil { + log.Debugf("Send to DNS server failed, %s", err) + continue + } + + resp, err = co.ReadMsg() + if err != nil { + log.Debugf("Read from DNS server failed, %s", err) + continue + } + + resp.Compress = true + break } + if resp == nil { return } diff --git a/libnetwork/sandbox.go b/libnetwork/sandbox.go index 4771053c36..7b4a4b99f2 100644 --- a/libnetwork/sandbox.go +++ b/libnetwork/sandbox.go @@ -394,6 +394,10 @@ func (sb *sandbox) ResolveIP(ip string) string { return svc } +func (sb *sandbox) execFunc(f func()) { + sb.osSbox.InvokeFunc(f) +} + func (sb *sandbox) ResolveName(name string) net.IP { var ip net.IP