net: make resolveInternetAddr return a list of addresses

This CL makes resolveInternetAddr return a list of addresses that
contain a pair of different address family IP addresses if possible,
but doesn't contain any API behavioral changes yet. A simple IP
address selection mechanism for Resolve{TCP,UDP,IP}Addr and Dial API
still prefers IPv4.

This is in preparation for TCP connection setup with fast failover on
dual IP stack node as described in RFC 6555.

Update #3610
Update #5267

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/13374043
diff --git a/src/pkg/net/dialgoogle_test.go b/src/pkg/net/dialgoogle_test.go
index f7939cc..000e1c3 100644
--- a/src/pkg/net/dialgoogle_test.go
+++ b/src/pkg/net/dialgoogle_test.go
@@ -16,6 +16,31 @@
 // If an IPv6 tunnel is running, we can try dialing a real IPv6 address.
 var testIPv6 = flag.Bool("ipv6", false, "assume ipv6 tunnel is present")
 
+func TestResolveGoogle(t *testing.T) {
+	if testing.Short() || !*testExternal {
+		t.Skip("skipping test to avoid external network")
+	}
+
+	for _, network := range []string{"tcp", "tcp4", "tcp6"} {
+		addr, err := ResolveTCPAddr(network, "www.google.com:http")
+		if err != nil {
+			if (network == "tcp" || network == "tcp4") && !supportsIPv4 {
+				t.Logf("ipv4 is not supported: %v", err)
+			} else if network == "tcp6" && !supportsIPv6 {
+				t.Logf("ipv6 is not supported: %v", err)
+			} else {
+				t.Errorf("ResolveTCPAddr failed: %v", err)
+			}
+			continue
+		}
+		if (network == "tcp" || network == "tcp4") && addr.IP.To4() == nil {
+			t.Errorf("got %v; expected an IPv4 address on %v", addr, network)
+		} else if network == "tcp6" && (addr.IP.To16() == nil || addr.IP.To4() != nil) {
+			t.Errorf("got %v; expected an IPv6 address on %v", addr, network)
+		}
+	}
+}
+
 // fd is already connected to the destination, port 80.
 // Run an HTTP request to fetch the appropriate page.
 func fetchGoogle(t *testing.T, fd Conn, network, addr string) {
diff --git a/src/pkg/net/ipraw_test.go b/src/pkg/net/ipraw_test.go
index 5bee21a..becd732 100644
--- a/src/pkg/net/ipraw_test.go
+++ b/src/pkg/net/ipraw_test.go
@@ -16,10 +16,10 @@
 )
 
 type resolveIPAddrTest struct {
-	net     string
-	litAddr string
-	addr    *IPAddr
-	err     error
+	net           string
+	litAddrOrName string
+	addr          *IPAddr
+	err           error
 }
 
 var resolveIPAddrTests = []resolveIPAddrTest{
@@ -51,13 +51,20 @@
 			{"ip6", "fe80::1%" + index, &IPAddr{IP: ParseIP("fe80::1"), Zone: index}, nil},
 		}...)
 	}
+	if ips, err := LookupIP("localhost"); err == nil && len(ips) > 1 && supportsIPv4 && supportsIPv6 {
+		resolveIPAddrTests = append(resolveIPAddrTests, []resolveIPAddrTest{
+			{"ip", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1).To4()}, nil},
+			{"ip4", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1).To4()}, nil},
+			{"ip6", "localhost", &IPAddr{IP: IPv6loopback}, nil},
+		}...)
+	}
 }
 
 func TestResolveIPAddr(t *testing.T) {
 	for _, tt := range resolveIPAddrTests {
-		addr, err := ResolveIPAddr(tt.net, tt.litAddr)
+		addr, err := ResolveIPAddr(tt.net, tt.litAddrOrName)
 		if err != tt.err {
-			t.Fatalf("ResolveIPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
+			t.Fatalf("ResolveIPAddr(%v, %v) failed: %v", tt.net, tt.litAddrOrName, err)
 		} else if !reflect.DeepEqual(addr, tt.addr) {
 			t.Fatalf("got %#v; expected %#v", addr, tt.addr)
 		}
diff --git a/src/pkg/net/ipsock.go b/src/pkg/net/ipsock.go
index 2040787..a4601ba 100644
--- a/src/pkg/net/ipsock.go
+++ b/src/pkg/net/ipsock.go
@@ -60,44 +60,61 @@
 
 var errNoSuitableAddress = errors.New("no suitable address found")
 
-// firstFavoriteAddr returns an address that implemets netaddr
-// interface.
-func firstFavoriteAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error) {
-	if filter == nil {
-		// We'll take any IP address, but since the dialing code
-		// does not yet try multiple addresses, prefer to use
-		// an IPv4 address if possible.  This is especially relevant
-		// if localhost resolves to [ipv6-localhost, ipv4-localhost].
-		// Too much code assumes localhost == ipv4-localhost.
-		addr, err := firstSupportedAddr(ipv4only, addrs, inetaddr)
-		if err != nil {
-			addr, err = firstSupportedAddr(anyaddr, addrs, inetaddr)
+// firstFavoriteAddr returns an address or a list of addresses that
+// implement the netaddr interface. Known filters are nil, ipv4only
+// and ipv6only. It returns any address when filter is nil. The result
+// contains at least one address when error is nil.
+func firstFavoriteAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) {
+	if filter != nil {
+		return firstSupportedAddr(filter, ips, inetaddr)
+	}
+	var (
+		ipv4, ipv6, swap bool
+		list             addrList
+	)
+	for _, ip := range ips {
+		// We'll take any IP address, but since the dialing
+		// code does not yet try multiple addresses
+		// effectively, prefer to use an IPv4 address if
+		// possible. This is especially relevant if localhost
+		// resolves to [ipv6-localhost, ipv4-localhost]. Too
+		// much code assumes localhost == ipv4-localhost.
+		if ip4 := ipv4only(ip); ip4 != nil && !ipv4 {
+			list = append(list, inetaddr(ip4))
+			ipv4 = true
+			if ipv6 {
+				swap = true
+			}
+		} else if ip6 := ipv6only(ip); ip6 != nil && !ipv6 {
+			list = append(list, inetaddr(ip6))
+			ipv6 = true
 		}
-		return addr, err
-	} else {
-		return firstSupportedAddr(filter, addrs, inetaddr)
+		if ipv4 && ipv6 {
+			if swap {
+				list[0], list[1] = list[1], list[0]
+			}
+			break
+		}
+	}
+	switch len(list) {
+	case 0:
+		return nil, errNoSuitableAddress
+	case 1:
+		return list[0], nil
+	default:
+		return list, nil
 	}
 }
 
-func firstSupportedAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error) {
-	for _, s := range addrs {
-		if ip := filter(ParseIP(s)); ip != nil {
+func firstSupportedAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) {
+	for _, ip := range ips {
+		if ip := filter(ip); ip != nil {
 			return inetaddr(ip), nil
 		}
 	}
 	return nil, errNoSuitableAddress
 }
 
-// anyaddr returns IP addresses that we can use with the current
-// kernel configuration.  It returns nil when ip is not suitable for
-// the configuration and an IP address.
-func anyaddr(ip IP) IP {
-	if ip4 := ipv4only(ip); ip4 != nil {
-		return ip4
-	}
-	return ipv6only(ip)
-}
-
 // ipv4only returns IPv4 addresses that we can use with the kernel's
 // IPv4 addressing modes.  It returns IPv4-mapped IPv6 addresses as
 // IPv4 addresses and returns other IPv6 address types as nils.
@@ -212,8 +229,11 @@
 }
 
 // resolveInternetAddr resolves addr that is either a literal IP
-// address or a DNS registered name and returns an internet protocol
-// family address.
+// address or a DNS name and returns an internet protocol family
+// address. It returns a list that contains a pair of different
+// address family addresses when addr is a DNS name and the name has
+// mutiple address family records. The result contains at least one
+// address when error is nil.
 func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) {
 	var (
 		err              error
@@ -260,9 +280,9 @@
 	if ip, zone = parseIPv6(host, true); ip != nil {
 		return inetaddr(ip), nil
 	}
-	// Try as a DNS registered name.
+	// Try as a DNS name.
 	host, zone = splitHostZone(host)
-	addrs, err := lookupHostDeadline(host, deadline)
+	ips, err := lookupIPDeadline(host, deadline)
 	if err != nil {
 		return nil, err
 	}
@@ -273,7 +293,7 @@
 	if net != "" && net[len(net)-1] == '6' || zone != "" {
 		filter = ipv6only
 	}
-	return firstFavoriteAddr(filter, addrs, inetaddr)
+	return firstFavoriteAddr(filter, ips, inetaddr)
 }
 
 func zoneToString(zone int) string {
diff --git a/src/pkg/net/ipsock_test.go b/src/pkg/net/ipsock_test.go
new file mode 100644
index 0000000..522266f
--- /dev/null
+++ b/src/pkg/net/ipsock_test.go
@@ -0,0 +1,189 @@
+// 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.
+
+package net
+
+import (
+	"reflect"
+	"testing"
+)
+
+var testInetaddr = func(ip IP) netaddr { return &TCPAddr{IP: ip, Port: 5682} }
+
+var firstFavoriteAddrTests = []struct {
+	filter   func(IP) IP
+	ips      []IP
+	inetaddr func(IP) netaddr
+	addr     netaddr
+	err      error
+}{
+	{
+		nil,
+		[]IP{
+			IPv4(127, 0, 0, 1),
+			IPv6loopback,
+		},
+		testInetaddr,
+		addrList{
+			&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+			&TCPAddr{IP: IPv6loopback, Port: 5682},
+		},
+		nil,
+	},
+	{
+		nil,
+		[]IP{
+			IPv6loopback,
+			IPv4(127, 0, 0, 1),
+		},
+		testInetaddr,
+		addrList{
+			&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+			&TCPAddr{IP: IPv6loopback, Port: 5682},
+		},
+		nil,
+	},
+	{
+		nil,
+		[]IP{
+			IPv4(127, 0, 0, 1),
+			IPv4(192, 168, 0, 1),
+		},
+		testInetaddr,
+		&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+		nil,
+	},
+	{
+		nil,
+		[]IP{
+			IPv6loopback,
+			ParseIP("fe80::1"),
+		},
+		testInetaddr,
+		&TCPAddr{IP: IPv6loopback, Port: 5682},
+		nil,
+	},
+	{
+		nil,
+		[]IP{
+			IPv4(127, 0, 0, 1),
+			IPv4(192, 168, 0, 1),
+			IPv6loopback,
+			ParseIP("fe80::1"),
+		},
+		testInetaddr,
+		addrList{
+			&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+			&TCPAddr{IP: IPv6loopback, Port: 5682},
+		},
+		nil,
+	},
+	{
+		nil,
+		[]IP{
+			IPv6loopback,
+			ParseIP("fe80::1"),
+			IPv4(127, 0, 0, 1),
+			IPv4(192, 168, 0, 1),
+		},
+		testInetaddr,
+		addrList{
+			&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+			&TCPAddr{IP: IPv6loopback, Port: 5682},
+		},
+		nil,
+	},
+	{
+		nil,
+		[]IP{
+			IPv4(127, 0, 0, 1),
+			IPv6loopback,
+			IPv4(192, 168, 0, 1),
+			ParseIP("fe80::1"),
+		},
+		testInetaddr,
+		addrList{
+			&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+			&TCPAddr{IP: IPv6loopback, Port: 5682},
+		},
+		nil,
+	},
+	{
+		nil,
+		[]IP{
+			IPv6loopback,
+			IPv4(127, 0, 0, 1),
+			ParseIP("fe80::1"),
+			IPv4(192, 168, 0, 1),
+		},
+		testInetaddr,
+		addrList{
+			&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+			&TCPAddr{IP: IPv6loopback, Port: 5682},
+		},
+		nil,
+	},
+
+	{
+		ipv4only,
+		[]IP{
+			IPv4(127, 0, 0, 1),
+			IPv6loopback,
+		},
+		testInetaddr,
+		&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+		nil,
+	},
+	{
+		ipv4only,
+		[]IP{
+			IPv6loopback,
+			IPv4(127, 0, 0, 1),
+		},
+		testInetaddr,
+		&TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5682},
+		nil,
+	},
+
+	{
+		ipv6only,
+		[]IP{
+			IPv4(127, 0, 0, 1),
+			IPv6loopback,
+		},
+		testInetaddr,
+		&TCPAddr{IP: IPv6loopback, Port: 5682},
+		nil,
+	},
+	{
+		ipv6only,
+		[]IP{
+			IPv6loopback,
+			IPv4(127, 0, 0, 1),
+		},
+		testInetaddr,
+		&TCPAddr{IP: IPv6loopback, Port: 5682},
+		nil,
+	},
+
+	{nil, nil, testInetaddr, nil, errNoSuitableAddress},
+
+	{ipv4only, nil, testInetaddr, nil, errNoSuitableAddress},
+	{ipv4only, []IP{IPv6loopback}, testInetaddr, nil, errNoSuitableAddress},
+
+	{ipv6only, nil, testInetaddr, nil, errNoSuitableAddress},
+	{ipv6only, []IP{IPv4(127, 0, 0, 1)}, testInetaddr, nil, errNoSuitableAddress},
+}
+
+func TestFirstFavoriteAddr(t *testing.T) {
+	for i, tt := range firstFavoriteAddrTests {
+		addr, err := firstFavoriteAddr(tt.filter, tt.ips, tt.inetaddr)
+		if err != tt.err {
+			t.Errorf("#%v: got %v; expected %v", i, err, tt.err)
+		}
+		if !reflect.DeepEqual(addr, tt.addr) {
+			t.Errorf("#%v: got %v; expected %v", i, addr, tt.addr)
+		}
+	}
+}
diff --git a/src/pkg/net/lookup.go b/src/pkg/net/lookup.go
index 0cd1993..0a10de2 100644
--- a/src/pkg/net/lookup.go
+++ b/src/pkg/net/lookup.go
@@ -23,19 +23,19 @@
 
 var lookupGroup singleflight
 
-// lookupHostMerge wraps lookupHost, but makes sure that for any given
+// lookupIPMerge wraps lookupIP, but makes sure that for any given
 // host, only one lookup is in-flight at a time. The returned memory
 // is always owned by the caller.
-func lookupHostMerge(host string) (addrs []string, err error) {
+func lookupIPMerge(host string) (addrs []IP, err error) {
 	addrsi, err, shared := lookupGroup.Do(host, func() (interface{}, error) {
-		return lookupHost(host)
+		return lookupIP(host)
 	})
 	if err != nil {
 		return nil, err
 	}
-	addrs = addrsi.([]string)
+	addrs = addrsi.([]IP)
 	if shared {
-		clone := make([]string, len(addrs))
+		clone := make([]IP, len(addrs))
 		copy(clone, addrs)
 		addrs = clone
 	}
@@ -45,12 +45,12 @@
 // LookupHost looks up the given host using the local resolver.
 // It returns an array of that host's addresses.
 func LookupHost(host string) (addrs []string, err error) {
-	return lookupHostMerge(host)
+	return lookupHost(host)
 }
 
-func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err error) {
+func lookupIPDeadline(host string, deadline time.Time) (addrs []IP, err error) {
 	if deadline.IsZero() {
-		return lookupHostMerge(host)
+		return lookupIPMerge(host)
 	}
 
 	// TODO(bradfitz): consider pushing the deadline down into the
@@ -68,12 +68,12 @@
 	t := time.NewTimer(timeout)
 	defer t.Stop()
 	type res struct {
-		addrs []string
+		addrs []IP
 		err   error
 	}
 	resc := make(chan res, 1)
 	go func() {
-		a, err := lookupHostMerge(host)
+		a, err := lookupIPMerge(host)
 		resc <- res{a, err}
 	}()
 	select {
@@ -88,7 +88,7 @@
 // LookupIP looks up host using the local resolver.
 // It returns an array of that host's IPv4 and IPv6 addresses.
 func LookupIP(host string) (addrs []IP, err error) {
-	return lookupIP(host)
+	return lookupIPMerge(host)
 }
 
 // LookupPort looks up the port for the given network and service.
diff --git a/src/pkg/net/tcp_test.go b/src/pkg/net/tcp_test.go
index 1af9616..a9c7562 100644
--- a/src/pkg/net/tcp_test.go
+++ b/src/pkg/net/tcp_test.go
@@ -273,10 +273,10 @@
 }
 
 type resolveTCPAddrTest struct {
-	net     string
-	litAddr string
-	addr    *TCPAddr
-	err     error
+	net           string
+	litAddrOrName string
+	addr          *TCPAddr
+	err           error
 }
 
 var resolveTCPAddrTests = []resolveTCPAddrTest{
@@ -303,13 +303,20 @@
 			{"tcp6", "[fe80::1%" + index + "]:4", &TCPAddr{IP: ParseIP("fe80::1"), Port: 4, Zone: index}, nil},
 		}...)
 	}
+	if ips, err := LookupIP("localhost"); err == nil && len(ips) > 1 && supportsIPv4 && supportsIPv6 {
+		resolveTCPAddrTests = append(resolveTCPAddrTests, []resolveTCPAddrTest{
+			{"tcp", "localhost:5", &TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5}, nil},
+			{"tcp4", "localhost:6", &TCPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 6}, nil},
+			{"tcp6", "localhost:7", &TCPAddr{IP: IPv6loopback, Port: 7}, nil},
+		}...)
+	}
 }
 
 func TestResolveTCPAddr(t *testing.T) {
 	for _, tt := range resolveTCPAddrTests {
-		addr, err := ResolveTCPAddr(tt.net, tt.litAddr)
+		addr, err := ResolveTCPAddr(tt.net, tt.litAddrOrName)
 		if err != tt.err {
-			t.Fatalf("ResolveTCPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
+			t.Fatalf("ResolveTCPAddr(%q, %q) failed: %v", tt.net, tt.litAddrOrName, err)
 		}
 		if !reflect.DeepEqual(addr, tt.addr) {
 			t.Fatalf("got %#v; expected %#v", addr, tt.addr)
diff --git a/src/pkg/net/udp_test.go b/src/pkg/net/udp_test.go
index f6a61ce..fc73a79 100644
--- a/src/pkg/net/udp_test.go
+++ b/src/pkg/net/udp_test.go
@@ -12,10 +12,10 @@
 )
 
 type resolveUDPAddrTest struct {
-	net     string
-	litAddr string
-	addr    *UDPAddr
-	err     error
+	net           string
+	litAddrOrName string
+	addr          *UDPAddr
+	err           error
 }
 
 var resolveUDPAddrTests = []resolveUDPAddrTest{
@@ -42,13 +42,20 @@
 			{"udp6", "[fe80::1%" + index + "]:4", &UDPAddr{IP: ParseIP("fe80::1"), Port: 4, Zone: index}, nil},
 		}...)
 	}
+	if ips, err := LookupIP("localhost"); err == nil && len(ips) > 1 && supportsIPv4 && supportsIPv6 {
+		resolveUDPAddrTests = append(resolveUDPAddrTests, []resolveUDPAddrTest{
+			{"udp", "localhost:5", &UDPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 5}, nil},
+			{"udp4", "localhost:6", &UDPAddr{IP: IPv4(127, 0, 0, 1).To4(), Port: 6}, nil},
+			{"udp6", "localhost:7", &UDPAddr{IP: IPv6loopback, Port: 7}, nil},
+		}...)
+	}
 }
 
 func TestResolveUDPAddr(t *testing.T) {
 	for _, tt := range resolveUDPAddrTests {
-		addr, err := ResolveUDPAddr(tt.net, tt.litAddr)
+		addr, err := ResolveUDPAddr(tt.net, tt.litAddrOrName)
 		if err != tt.err {
-			t.Fatalf("ResolveUDPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
+			t.Fatalf("ResolveUDPAddr(%q, %q) failed: %v", tt.net, tt.litAddrOrName, err)
 		}
 		if !reflect.DeepEqual(addr, tt.addr) {
 			t.Fatalf("got %#v; expected %#v", addr, tt.addr)