mirror of
https://github.com/golang/go.git
synced 2025-10-31 16:50:58 +00:00
My previous https://golang.org/cl/22101 to add context throughout the net package broke Plan 9, which isn't currently tested (#15251). It also broke some old unsupported version of Windows (Windows 2000?) which doesn't have the ConnectEx function, but that was only found visually, since our minimum supported Windows version has ConnectEx. This change simplifies the Windows and deletes the non-ConnectEx code path. Windows 2000 will work even less now, if it even worked before. Windows XP remains our minimum supported version. Specifically, the previous CL stopped using the "dial" function, which 0intro noted: https://github.com/golang/go/issues/15333#issuecomment-210842761 This CL removes the dial function instead and makes plan9's net implementation respect contexts, which likely fixes a number of t.Skipped tests. I'm leaving that to 0intro to investigate. In the process of propagating and respecting contexts for plan9, I had to change some signatures to add contexts to more places and ended up pushing contexts down into the Go-based DNS resolution as well, replacing the pure-Go DNS implementation's use of "timeout time.Duration" with a context instead. Updates #11932 Updates #15328 Fixes #15333 Change-Id: I6ad1e62f38271cdd86b3f40921f2d0f23374936a Reviewed-on: https://go-review.googlesource.com/22144 Reviewed-by: David du Colombier <0intro@gmail.com> Reviewed-by: Mikio Hara <mikioh.mikioh@gmail.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
601 lines
14 KiB
Go
601 lines
14 KiB
Go
// Copyright 2013 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
|
|
package net
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"internal/testenv"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var dnsTransportFallbackTests = []struct {
|
|
server string
|
|
name string
|
|
qtype uint16
|
|
timeout int
|
|
rcode int
|
|
}{
|
|
// Querying "com." with qtype=255 usually makes an answer
|
|
// which requires more than 512 bytes.
|
|
{"8.8.8.8:53", "com.", dnsTypeALL, 2, dnsRcodeSuccess},
|
|
{"8.8.4.4:53", "com.", dnsTypeALL, 4, dnsRcodeSuccess},
|
|
}
|
|
|
|
func TestDNSTransportFallback(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
for _, tt := range dnsTransportFallbackTests {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(tt.timeout)*time.Second)
|
|
defer cancel()
|
|
msg, err := exchange(ctx, tt.server, tt.name, tt.qtype)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
switch msg.rcode {
|
|
case tt.rcode, dnsRcodeServerFailure:
|
|
default:
|
|
t.Errorf("got %v from %v; want %v", msg.rcode, tt.server, tt.rcode)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// See RFC 6761 for further information about the reserved, pseudo
|
|
// domain names.
|
|
var specialDomainNameTests = []struct {
|
|
name string
|
|
qtype uint16
|
|
rcode int
|
|
}{
|
|
// Name resolution APIs and libraries should not recognize the
|
|
// followings as special.
|
|
{"1.0.168.192.in-addr.arpa.", dnsTypePTR, dnsRcodeNameError},
|
|
{"test.", dnsTypeALL, dnsRcodeNameError},
|
|
{"example.com.", dnsTypeALL, dnsRcodeSuccess},
|
|
|
|
// Name resolution APIs and libraries should recognize the
|
|
// followings as special and should not send any queries.
|
|
// Though, we test those names here for verifying negative
|
|
// answers at DNS query-response interaction level.
|
|
{"localhost.", dnsTypeALL, dnsRcodeNameError},
|
|
{"invalid.", dnsTypeALL, dnsRcodeNameError},
|
|
}
|
|
|
|
func TestSpecialDomainName(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
server := "8.8.8.8:53"
|
|
for _, tt := range specialDomainNameTests {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
msg, err := exchange(ctx, server, tt.name, tt.qtype)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
switch msg.rcode {
|
|
case tt.rcode, dnsRcodeServerFailure:
|
|
default:
|
|
t.Errorf("got %v from %v; want %v", msg.rcode, server, tt.rcode)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// Issue 13705: don't try to resolve onion addresses, etc
|
|
func TestAvoidDNSName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
avoid bool
|
|
}{
|
|
{"foo.com", false},
|
|
{"foo.com.", false},
|
|
|
|
{"foo.onion.", true},
|
|
{"foo.onion", true},
|
|
{"foo.ONION", true},
|
|
{"foo.ONION.", true},
|
|
|
|
{"foo.local.", true},
|
|
{"foo.local", true},
|
|
{"foo.LOCAL", true},
|
|
{"foo.LOCAL.", true},
|
|
|
|
{"", true}, // will be rejected earlier too
|
|
|
|
// Without stuff before onion/local, they're fine to
|
|
// use DNS. With a search path,
|
|
// "onion.vegegtables.com" can use DNS. Without a
|
|
// search path (or with a trailing dot), the queries
|
|
// are just kinda useless, but don't reveal anything
|
|
// private.
|
|
{"local", false},
|
|
{"onion", false},
|
|
{"local.", false},
|
|
{"onion.", false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := avoidDNS(tt.name)
|
|
if got != tt.avoid {
|
|
t.Errorf("avoidDNS(%q) = %v; want %v", tt.name, got, tt.avoid)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Issue 13705: don't try to resolve onion addresses, etc
|
|
func TestLookupTorOnion(t *testing.T) {
|
|
addrs, err := goLookupIP(context.Background(), "foo.onion")
|
|
if len(addrs) > 0 {
|
|
t.Errorf("unexpected addresses: %v", addrs)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("lookup = %v; want nil", err)
|
|
}
|
|
}
|
|
|
|
type resolvConfTest struct {
|
|
dir string
|
|
path string
|
|
*resolverConfig
|
|
}
|
|
|
|
func newResolvConfTest() (*resolvConfTest, error) {
|
|
dir, err := ioutil.TempDir("", "go-resolvconftest")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
conf := &resolvConfTest{
|
|
dir: dir,
|
|
path: path.Join(dir, "resolv.conf"),
|
|
resolverConfig: &resolvConf,
|
|
}
|
|
conf.initOnce.Do(conf.init)
|
|
return conf, nil
|
|
}
|
|
|
|
func (conf *resolvConfTest) writeAndUpdate(lines []string) error {
|
|
f, err := os.OpenFile(conf.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := f.WriteString(strings.Join(lines, "\n")); err != nil {
|
|
f.Close()
|
|
return err
|
|
}
|
|
f.Close()
|
|
if err := conf.forceUpdate(conf.path, time.Now().Add(time.Hour)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (conf *resolvConfTest) forceUpdate(name string, lastChecked time.Time) error {
|
|
dnsConf := dnsReadConfig(name)
|
|
conf.mu.Lock()
|
|
conf.dnsConfig = dnsConf
|
|
conf.mu.Unlock()
|
|
for i := 0; i < 5; i++ {
|
|
if conf.tryAcquireSema() {
|
|
conf.lastChecked = lastChecked
|
|
conf.releaseSema()
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("tryAcquireSema for %s failed", name)
|
|
}
|
|
|
|
func (conf *resolvConfTest) servers() []string {
|
|
conf.mu.RLock()
|
|
servers := conf.dnsConfig.servers
|
|
conf.mu.RUnlock()
|
|
return servers
|
|
}
|
|
|
|
func (conf *resolvConfTest) teardown() error {
|
|
err := conf.forceUpdate("/etc/resolv.conf", time.Time{})
|
|
os.RemoveAll(conf.dir)
|
|
return err
|
|
}
|
|
|
|
var updateResolvConfTests = []struct {
|
|
name string // query name
|
|
lines []string // resolver configuration lines
|
|
servers []string // expected name servers
|
|
}{
|
|
{
|
|
name: "golang.org",
|
|
lines: []string{"nameserver 8.8.8.8"},
|
|
servers: []string{"8.8.8.8"},
|
|
},
|
|
{
|
|
name: "",
|
|
lines: nil, // an empty resolv.conf should use defaultNS as name servers
|
|
servers: defaultNS,
|
|
},
|
|
{
|
|
name: "www.example.com",
|
|
lines: []string{"nameserver 8.8.4.4"},
|
|
servers: []string{"8.8.4.4"},
|
|
},
|
|
}
|
|
|
|
func TestUpdateResolvConf(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
conf, err := newResolvConfTest()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conf.teardown()
|
|
|
|
for i, tt := range updateResolvConfTests {
|
|
if err := conf.writeAndUpdate(tt.lines); err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
if tt.name != "" {
|
|
var wg sync.WaitGroup
|
|
const N = 10
|
|
wg.Add(N)
|
|
for j := 0; j < N; j++ {
|
|
go func(name string) {
|
|
defer wg.Done()
|
|
ips, err := goLookupIP(context.Background(), name)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if len(ips) == 0 {
|
|
t.Errorf("no records for %s", name)
|
|
return
|
|
}
|
|
}(tt.name)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
servers := conf.servers()
|
|
if !reflect.DeepEqual(servers, tt.servers) {
|
|
t.Errorf("#%d: got %v; want %v", i, servers, tt.servers)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
var goLookupIPWithResolverConfigTests = []struct {
|
|
name string
|
|
lines []string // resolver configuration lines
|
|
error
|
|
a, aaaa bool // whether response contains A, AAAA-record
|
|
}{
|
|
// no records, transport timeout
|
|
{
|
|
"jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
|
|
[]string{
|
|
"options timeout:1 attempts:1",
|
|
"nameserver 255.255.255.255", // please forgive us for abuse of limited broadcast address
|
|
},
|
|
&DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "255.255.255.255:53", IsTimeout: true},
|
|
false, false,
|
|
},
|
|
|
|
// no records, non-existent domain
|
|
{
|
|
"jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j",
|
|
[]string{
|
|
"options timeout:3 attempts:1",
|
|
"nameserver 8.8.8.8",
|
|
},
|
|
&DNSError{Name: "jgahvsekduiv9bw4b3qhn4ykdfgj0493iohkrjfhdvhjiu4j", Server: "8.8.8.8:53", IsTimeout: false},
|
|
false, false,
|
|
},
|
|
|
|
// a few A records, no AAAA records
|
|
{
|
|
"ipv4.google.com.",
|
|
[]string{
|
|
"nameserver 8.8.8.8",
|
|
"nameserver 2001:4860:4860::8888",
|
|
},
|
|
nil,
|
|
true, false,
|
|
},
|
|
{
|
|
"ipv4.google.com",
|
|
[]string{
|
|
"domain golang.org",
|
|
"nameserver 2001:4860:4860::8888",
|
|
"nameserver 8.8.8.8",
|
|
},
|
|
nil,
|
|
true, false,
|
|
},
|
|
{
|
|
"ipv4.google.com",
|
|
[]string{
|
|
"search x.golang.org y.golang.org",
|
|
"nameserver 2001:4860:4860::8888",
|
|
"nameserver 8.8.8.8",
|
|
},
|
|
nil,
|
|
true, false,
|
|
},
|
|
|
|
// no A records, a few AAAA records
|
|
{
|
|
"ipv6.google.com.",
|
|
[]string{
|
|
"nameserver 2001:4860:4860::8888",
|
|
"nameserver 8.8.8.8",
|
|
},
|
|
nil,
|
|
false, true,
|
|
},
|
|
{
|
|
"ipv6.google.com",
|
|
[]string{
|
|
"domain golang.org",
|
|
"nameserver 8.8.8.8",
|
|
"nameserver 2001:4860:4860::8888",
|
|
},
|
|
nil,
|
|
false, true,
|
|
},
|
|
{
|
|
"ipv6.google.com",
|
|
[]string{
|
|
"search x.golang.org y.golang.org",
|
|
"nameserver 8.8.8.8",
|
|
"nameserver 2001:4860:4860::8888",
|
|
},
|
|
nil,
|
|
false, true,
|
|
},
|
|
|
|
// both A and AAAA records
|
|
{
|
|
"hostname.as112.net", // see RFC 7534
|
|
[]string{
|
|
"domain golang.org",
|
|
"nameserver 2001:4860:4860::8888",
|
|
"nameserver 8.8.8.8",
|
|
},
|
|
nil,
|
|
true, true,
|
|
},
|
|
{
|
|
"hostname.as112.net", // see RFC 7534
|
|
[]string{
|
|
"search x.golang.org y.golang.org",
|
|
"nameserver 2001:4860:4860::8888",
|
|
"nameserver 8.8.8.8",
|
|
},
|
|
nil,
|
|
true, true,
|
|
},
|
|
}
|
|
|
|
func TestGoLookupIPWithResolverConfig(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
conf, err := newResolvConfTest()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conf.teardown()
|
|
|
|
for _, tt := range goLookupIPWithResolverConfigTests {
|
|
if err := conf.writeAndUpdate(tt.lines); err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
addrs, err := goLookupIP(context.Background(), tt.name)
|
|
if err != nil {
|
|
// This test uses external network connectivity.
|
|
// We need to take care with errors on both
|
|
// DNS message exchange layer and DNS
|
|
// transport layer because goLookupIP may fail
|
|
// when the IP connectivty on node under test
|
|
// gets lost during its run.
|
|
if err, ok := err.(*DNSError); !ok || tt.error != nil && (err.Name != tt.error.(*DNSError).Name || err.Server != tt.error.(*DNSError).Server || err.IsTimeout != tt.error.(*DNSError).IsTimeout) {
|
|
t.Errorf("got %v; want %v", err, tt.error)
|
|
}
|
|
continue
|
|
}
|
|
if len(addrs) == 0 {
|
|
t.Errorf("no records for %s", tt.name)
|
|
}
|
|
if !tt.a && !tt.aaaa && len(addrs) > 0 {
|
|
t.Errorf("unexpected %v for %s", addrs, tt.name)
|
|
}
|
|
for _, addr := range addrs {
|
|
if !tt.a && addr.IP.To4() != nil {
|
|
t.Errorf("got %v; must not be IPv4 address", addr)
|
|
}
|
|
if !tt.aaaa && addr.IP.To16() != nil && addr.IP.To4() == nil {
|
|
t.Errorf("got %v; must not be IPv6 address", addr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test that goLookupIPOrder falls back to the host file when no DNS servers are available.
|
|
func TestGoLookupIPOrderFallbackToFile(t *testing.T) {
|
|
testenv.MustHaveExternalNetwork(t)
|
|
|
|
// Add a config that simulates no dns servers being available.
|
|
conf, err := newResolvConfTest()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := conf.writeAndUpdate([]string{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Redirect host file lookups.
|
|
defer func(orig string) { testHookHostsPath = orig }(testHookHostsPath)
|
|
testHookHostsPath = "testdata/hosts"
|
|
|
|
for _, order := range []hostLookupOrder{hostLookupFilesDNS, hostLookupDNSFiles} {
|
|
name := fmt.Sprintf("order %v", order)
|
|
|
|
// First ensure that we get an error when contacting a non-existent host.
|
|
_, err := goLookupIPOrder(context.Background(), "notarealhost", order)
|
|
if err == nil {
|
|
t.Errorf("%s: expected error while looking up name not in hosts file", name)
|
|
continue
|
|
}
|
|
|
|
// Now check that we get an address when the name appears in the hosts file.
|
|
addrs, err := goLookupIPOrder(context.Background(), "thor", order) // entry is in "testdata/hosts"
|
|
if err != nil {
|
|
t.Errorf("%s: expected to successfully lookup host entry", name)
|
|
continue
|
|
}
|
|
if len(addrs) != 1 {
|
|
t.Errorf("%s: expected exactly one result, but got %v", name, addrs)
|
|
continue
|
|
}
|
|
if got, want := addrs[0].String(), "127.1.1.1"; got != want {
|
|
t.Errorf("%s: address doesn't match expectation. got %v, want %v", name, got, want)
|
|
}
|
|
}
|
|
defer conf.teardown()
|
|
}
|
|
|
|
// Issue 12712.
|
|
// When using search domains, return the error encountered
|
|
// querying the original name instead of an error encountered
|
|
// querying a generated name.
|
|
func TestErrorForOriginalNameWhenSearching(t *testing.T) {
|
|
const fqdn = "doesnotexist.domain"
|
|
|
|
origTestHookDNSDialer := testHookDNSDialer
|
|
defer func() { testHookDNSDialer = origTestHookDNSDialer }()
|
|
|
|
conf, err := newResolvConfTest()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conf.teardown()
|
|
|
|
if err := conf.writeAndUpdate([]string{"search servfail"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d := &fakeDNSConn{}
|
|
testHookDNSDialer = func() dnsDialer { return d }
|
|
|
|
d.rh = func(q *dnsMsg) (*dnsMsg, error) {
|
|
r := &dnsMsg{
|
|
dnsMsgHdr: dnsMsgHdr{
|
|
id: q.id,
|
|
},
|
|
}
|
|
|
|
switch q.question[0].Name {
|
|
case fqdn + ".servfail.":
|
|
r.rcode = dnsRcodeServerFailure
|
|
default:
|
|
r.rcode = dnsRcodeNameError
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
_, err = goLookupIP(context.Background(), fqdn)
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
|
|
want := &DNSError{Name: fqdn, Err: errNoSuchHost.Error()}
|
|
if err, ok := err.(*DNSError); !ok || err.Name != want.Name || err.Err != want.Err {
|
|
t.Errorf("got %v; want %v", err, want)
|
|
}
|
|
}
|
|
|
|
func BenchmarkGoLookupIP(b *testing.B) {
|
|
testHookUninstaller.Do(uninstallTestHooks)
|
|
ctx := context.Background()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
goLookupIP(ctx, "www.example.com")
|
|
}
|
|
}
|
|
|
|
func BenchmarkGoLookupIPNoSuchHost(b *testing.B) {
|
|
testHookUninstaller.Do(uninstallTestHooks)
|
|
ctx := context.Background()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
goLookupIP(ctx, "some.nonexistent")
|
|
}
|
|
}
|
|
|
|
func BenchmarkGoLookupIPWithBrokenNameServer(b *testing.B) {
|
|
testHookUninstaller.Do(uninstallTestHooks)
|
|
|
|
conf, err := newResolvConfTest()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
defer conf.teardown()
|
|
|
|
lines := []string{
|
|
"nameserver 203.0.113.254", // use TEST-NET-3 block, see RFC 5737
|
|
"nameserver 8.8.8.8",
|
|
}
|
|
if err := conf.writeAndUpdate(lines); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
ctx := context.Background()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
goLookupIP(ctx, "www.example.com")
|
|
}
|
|
}
|
|
|
|
type fakeDNSConn struct {
|
|
// last query
|
|
qmu sync.Mutex // guards q
|
|
q *dnsMsg
|
|
// reply handler
|
|
rh func(*dnsMsg) (*dnsMsg, error)
|
|
}
|
|
|
|
func (f *fakeDNSConn) dialDNS(_ context.Context, n, s string) (dnsConn, error) {
|
|
return f, nil
|
|
}
|
|
|
|
func (f *fakeDNSConn) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeDNSConn) SetDeadline(time.Time) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeDNSConn) writeDNSQuery(q *dnsMsg) error {
|
|
f.qmu.Lock()
|
|
defer f.qmu.Unlock()
|
|
f.q = q
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeDNSConn) readDNSResponse() (*dnsMsg, error) {
|
|
f.qmu.Lock()
|
|
q := f.q
|
|
f.qmu.Unlock()
|
|
return f.rh(q)
|
|
}
|