Commit 7c59c8bd authored by Mikio Hara's avatar Mikio Hara

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
parent 379096de
...@@ -16,6 +16,31 @@ import ( ...@@ -16,6 +16,31 @@ import (
// If an IPv6 tunnel is running, we can try dialing a real IPv6 address. // 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") 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. // fd is already connected to the destination, port 80.
// Run an HTTP request to fetch the appropriate page. // Run an HTTP request to fetch the appropriate page.
func fetchGoogle(t *testing.T, fd Conn, network, addr string) { func fetchGoogle(t *testing.T, fd Conn, network, addr string) {
......
...@@ -17,7 +17,7 @@ import ( ...@@ -17,7 +17,7 @@ import (
type resolveIPAddrTest struct { type resolveIPAddrTest struct {
net string net string
litAddr string litAddrOrName string
addr *IPAddr addr *IPAddr
err error err error
} }
...@@ -51,13 +51,20 @@ func init() { ...@@ -51,13 +51,20 @@ func init() {
{"ip6", "fe80::1%" + index, &IPAddr{IP: ParseIP("fe80::1"), Zone: index}, nil}, {"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) { func TestResolveIPAddr(t *testing.T) {
for _, tt := range resolveIPAddrTests { for _, tt := range resolveIPAddrTests {
addr, err := ResolveIPAddr(tt.net, tt.litAddr) addr, err := ResolveIPAddr(tt.net, tt.litAddrOrName)
if err != tt.err { 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) { } else if !reflect.DeepEqual(addr, tt.addr) {
t.Fatalf("got %#v; expected %#v", addr, tt.addr) t.Fatalf("got %#v; expected %#v", addr, tt.addr)
} }
......
...@@ -60,44 +60,61 @@ func (al addrList) toAddr() Addr { ...@@ -60,44 +60,61 @@ func (al addrList) toAddr() Addr {
var errNoSuitableAddress = errors.New("no suitable address found") var errNoSuitableAddress = errors.New("no suitable address found")
// firstFavoriteAddr returns an address that implemets netaddr // firstFavoriteAddr returns an address or a list of addresses that
// interface. // implement the netaddr interface. Known filters are nil, ipv4only
func firstFavoriteAddr(filter func(IP) IP, addrs []string, inetaddr func(IP) netaddr) (netaddr, error) { // and ipv6only. It returns any address when filter is nil. The result
if filter == nil { // contains at least one address when error is nil.
// We'll take any IP address, but since the dialing code func firstFavoriteAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) {
// does not yet try multiple addresses, prefer to use if filter != nil {
// an IPv4 address if possible. This is especially relevant return firstSupportedAddr(filter, ips, inetaddr)
// 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)
} }
return addr, err var (
} else { ipv4, ipv6, swap bool
return firstSupportedAddr(filter, addrs, inetaddr) 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
}
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) { func firstSupportedAddr(filter func(IP) IP, ips []IP, inetaddr func(IP) netaddr) (netaddr, error) {
for _, s := range addrs { for _, ip := range ips {
if ip := filter(ParseIP(s)); ip != nil { if ip := filter(ip); ip != nil {
return inetaddr(ip), nil return inetaddr(ip), nil
} }
} }
return nil, errNoSuitableAddress 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 // ipv4only returns IPv4 addresses that we can use with the kernel's
// IPv4 addressing modes. It returns IPv4-mapped IPv6 addresses as // IPv4 addressing modes. It returns IPv4-mapped IPv6 addresses as
// IPv4 addresses and returns other IPv6 address types as nils. // IPv4 addresses and returns other IPv6 address types as nils.
...@@ -212,8 +229,11 @@ func JoinHostPort(host, port string) string { ...@@ -212,8 +229,11 @@ func JoinHostPort(host, port string) string {
} }
// resolveInternetAddr resolves addr that is either a literal IP // resolveInternetAddr resolves addr that is either a literal IP
// address or a DNS registered name and returns an internet protocol // address or a DNS name and returns an internet protocol family
// family address. // 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) { func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) {
var ( var (
err error err error
...@@ -260,9 +280,9 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) ...@@ -260,9 +280,9 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
if ip, zone = parseIPv6(host, true); ip != nil { if ip, zone = parseIPv6(host, true); ip != nil {
return inetaddr(ip), nil return inetaddr(ip), nil
} }
// Try as a DNS registered name. // Try as a DNS name.
host, zone = splitHostZone(host) host, zone = splitHostZone(host)
addrs, err := lookupHostDeadline(host, deadline) ips, err := lookupIPDeadline(host, deadline)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -273,7 +293,7 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error) ...@@ -273,7 +293,7 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
if net != "" && net[len(net)-1] == '6' || zone != "" { if net != "" && net[len(net)-1] == '6' || zone != "" {
filter = ipv6only filter = ipv6only
} }
return firstFavoriteAddr(filter, addrs, inetaddr) return firstFavoriteAddr(filter, ips, inetaddr)
} }
func zoneToString(zone int) string { func zoneToString(zone int) string {
......
// 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)
}
}
}
...@@ -23,19 +23,19 @@ var protocols = map[string]int{ ...@@ -23,19 +23,19 @@ var protocols = map[string]int{
var lookupGroup singleflight 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 // host, only one lookup is in-flight at a time. The returned memory
// is always owned by the caller. // 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) { addrsi, err, shared := lookupGroup.Do(host, func() (interface{}, error) {
return lookupHost(host) return lookupIP(host)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
addrs = addrsi.([]string) addrs = addrsi.([]IP)
if shared { if shared {
clone := make([]string, len(addrs)) clone := make([]IP, len(addrs))
copy(clone, addrs) copy(clone, addrs)
addrs = clone addrs = clone
} }
...@@ -45,12 +45,12 @@ func lookupHostMerge(host string) (addrs []string, err error) { ...@@ -45,12 +45,12 @@ func lookupHostMerge(host string) (addrs []string, err error) {
// LookupHost looks up the given host using the local resolver. // LookupHost looks up the given host using the local resolver.
// It returns an array of that host's addresses. // It returns an array of that host's addresses.
func LookupHost(host string) (addrs []string, err error) { 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() { if deadline.IsZero() {
return lookupHostMerge(host) return lookupIPMerge(host)
} }
// TODO(bradfitz): consider pushing the deadline down into the // TODO(bradfitz): consider pushing the deadline down into the
...@@ -68,12 +68,12 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er ...@@ -68,12 +68,12 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er
t := time.NewTimer(timeout) t := time.NewTimer(timeout)
defer t.Stop() defer t.Stop()
type res struct { type res struct {
addrs []string addrs []IP
err error err error
} }
resc := make(chan res, 1) resc := make(chan res, 1)
go func() { go func() {
a, err := lookupHostMerge(host) a, err := lookupIPMerge(host)
resc <- res{a, err} resc <- res{a, err}
}() }()
select { select {
...@@ -88,7 +88,7 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er ...@@ -88,7 +88,7 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er
// LookupIP looks up host using the local resolver. // LookupIP looks up host using the local resolver.
// It returns an array of that host's IPv4 and IPv6 addresses. // It returns an array of that host's IPv4 and IPv6 addresses.
func LookupIP(host string) (addrs []IP, err error) { 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. // LookupPort looks up the port for the given network and service.
......
...@@ -274,7 +274,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) { ...@@ -274,7 +274,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) {
type resolveTCPAddrTest struct { type resolveTCPAddrTest struct {
net string net string
litAddr string litAddrOrName string
addr *TCPAddr addr *TCPAddr
err error err error
} }
...@@ -303,13 +303,20 @@ func init() { ...@@ -303,13 +303,20 @@ func init() {
{"tcp6", "[fe80::1%" + index + "]:4", &TCPAddr{IP: ParseIP("fe80::1"), Port: 4, Zone: index}, nil}, {"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) { func TestResolveTCPAddr(t *testing.T) {
for _, tt := range resolveTCPAddrTests { for _, tt := range resolveTCPAddrTests {
addr, err := ResolveTCPAddr(tt.net, tt.litAddr) addr, err := ResolveTCPAddr(tt.net, tt.litAddrOrName)
if err != tt.err { 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) { if !reflect.DeepEqual(addr, tt.addr) {
t.Fatalf("got %#v; expected %#v", addr, tt.addr) t.Fatalf("got %#v; expected %#v", addr, tt.addr)
......
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
type resolveUDPAddrTest struct { type resolveUDPAddrTest struct {
net string net string
litAddr string litAddrOrName string
addr *UDPAddr addr *UDPAddr
err error err error
} }
...@@ -42,13 +42,20 @@ func init() { ...@@ -42,13 +42,20 @@ func init() {
{"udp6", "[fe80::1%" + index + "]:4", &UDPAddr{IP: ParseIP("fe80::1"), Port: 4, Zone: index}, nil}, {"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) { func TestResolveUDPAddr(t *testing.T) {
for _, tt := range resolveUDPAddrTests { for _, tt := range resolveUDPAddrTests {
addr, err := ResolveUDPAddr(tt.net, tt.litAddr) addr, err := ResolveUDPAddr(tt.net, tt.litAddrOrName)
if err != tt.err { 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) { if !reflect.DeepEqual(addr, tt.addr) {
t.Fatalf("got %#v; expected %#v", addr, tt.addr) t.Fatalf("got %#v; expected %#v", addr, tt.addr)
......
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