Commit 39a021bc authored by Alex A Skinner's avatar Alex A Skinner Committed by Mikio Hara

net: implement query-response fast failover in builtin dns stub resolver

Speed improvements via code cleanup, and changes to make go dns behave more like glibc resolver.  See https://groups.google.com/forum/#!topic/golang-dev/lV-0aHqxVeo

Fixes #6579.

Benchmark results on linux/amd64

benchmark                                  old ns/op    new ns/op    delta
BenchmarkGoLookupIP                          4831903      2572937  -46.75%
BenchmarkGoLookupIPNoSuchHost               10114105      2419641  -76.08%
BenchmarkGoLookupIPWithBrokenNameServer  20007735624   5004490730  -74.99%

benchmark                                 old allocs   new allocs    delta
BenchmarkGoLookupIP                              287          288    0.35%
BenchmarkGoLookupIPNoSuchHost                    204          102  -50.00%
BenchmarkGoLookupIPWithBrokenNameServer          410          358  -12.68%

benchmark                                  old bytes    new bytes    delta
BenchmarkGoLookupIP                            13181        13271    0.68%
BenchmarkGoLookupIPNoSuchHost                  17260         8714  -49.51%
BenchmarkGoLookupIPWithBrokenNameServer        28160        22432  -20.34%

LGTM=mikioh.mikioh
R=golang-codereviews, mikioh.mikioh, bradfitz, josharian, abursavich
CC=golang-codereviews
https://golang.org/cl/128820043
parent 2758cb75
...@@ -169,33 +169,20 @@ func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, err ...@@ -169,33 +169,20 @@ func tryOneName(cfg *dnsConfig, name string, qtype uint16) (string, []dnsRR, err
} }
timeout := time.Duration(cfg.timeout) * time.Second timeout := time.Duration(cfg.timeout) * time.Second
var lastErr error var lastErr error
for _, server := range cfg.servers {
server += ":53"
lastErr = &DNSError{
Err: "no answer from DNS server",
Name: name,
Server: server,
IsTimeout: true,
}
for i := 0; i < cfg.attempts; i++ { for i := 0; i < cfg.attempts; i++ {
for _, server := range cfg.servers {
server = JoinHostPort(server, "53")
msg, err := exchange(server, name, qtype, timeout) msg, err := exchange(server, name, qtype, timeout)
if err != nil { if err != nil {
if nerr, ok := err.(Error); ok && nerr.Timeout() {
lastErr = &DNSError{
Err: nerr.Error(),
Name: name,
Server: server,
IsTimeout: true,
}
continue
}
lastErr = &DNSError{ lastErr = &DNSError{
Err: err.Error(), Err: err.Error(),
Name: name, Name: name,
Server: server, Server: server,
} }
break if nerr, ok := err.(Error); ok && nerr.Timeout() {
lastErr.(*DNSError).IsTimeout = true
}
continue
} }
cname, addrs, err := answer(name, server, msg, qtype) cname, addrs, err := answer(name, server, msg, qtype)
if err == nil || err.(*DNSError).Err == noSuchHost { if err == nil || err.(*DNSError).Err == noSuchHost {
...@@ -387,31 +374,36 @@ func goLookupIP(name string) (addrs []IP, err error) { ...@@ -387,31 +374,36 @@ func goLookupIP(name string) (addrs []IP, err error) {
return return
} }
} }
var records []dnsRR type racer struct {
var cname string qtype uint16
var err4, err6 error rrs []dnsRR
cname, records, err4 = lookup(name, dnsTypeA) error
addrs = convertRR_A(records)
if cname != "" {
name = cname
} }
_, records, err6 = lookup(name, dnsTypeAAAA) lane := make(chan racer, 1)
if err4 != nil && err6 == nil { qtypes := [...]uint16{dnsTypeA, dnsTypeAAAA}
// Ignore A error because AAAA lookup succeeded. for _, qtype := range qtypes {
err4 = nil go func(qtype uint16) {
_, rrs, err := lookup(name, qtype)
lane <- racer{qtype, rrs, err}
}(qtype)
} }
if err6 != nil && len(addrs) > 0 { var lastErr error
// Ignore AAAA error because A lookup succeeded. for range qtypes {
err6 = nil racer := <-lane
if racer.error != nil {
lastErr = racer.error
continue
} }
if err4 != nil { switch racer.qtype {
return nil, err4 case dnsTypeA:
addrs = append(addrs, convertRR_A(racer.rrs)...)
case dnsTypeAAAA:
addrs = append(addrs, convertRR_AAAA(racer.rrs)...)
} }
if err6 != nil {
return nil, err6
} }
if len(addrs) == 0 && lastErr != nil {
addrs = append(addrs, convertRR_AAAA(records)...) return nil, lastErr
}
return addrs, nil return addrs, nil
} }
......
...@@ -218,8 +218,29 @@ func TestReloadResolvConfChange(t *testing.T) { ...@@ -218,8 +218,29 @@ func TestReloadResolvConfChange(t *testing.T) {
r.WantServers([]string{"[8.8.4.4]"}) r.WantServers([]string{"[8.8.4.4]"})
} }
func BenchmarkGoLookupIP(b *testing.B) {
for i := 0; i < b.N; i++ {
goLookupIP("www.example.com")
}
}
func BenchmarkGoLookupIPNoSuchHost(b *testing.B) { func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
goLookupIP("some.nonexistent") goLookupIP("some.nonexistent")
} }
} }
func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
onceLoadConfig.Do(loadDefaultConfig)
if cfg.dnserr != nil || cfg.dnsConfig == nil {
b.Fatalf("loadConfig failed: %v", cfg.dnserr)
}
// This looks ugly but it's safe as long as benchmarks are run
// sequentially in package testing.
orig := cfg.dnsConfig
cfg.dnsConfig.servers = append([]string{"203.0.113.254"}, cfg.dnsConfig.servers...) // use TEST-NET-3 block, see RFC 5737
for i := 0; i < b.N; i++ {
goLookupIP("www.example.com")
}
cfg.dnsConfig = orig
}
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