mirror of
https://github.com/golang/go
synced 2024-11-24 23:07:56 -07:00
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
This commit is contained in:
parent
379096de05
commit
7c59c8bdee
@ -16,6 +16,31 @@ import (
|
||||
// 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) {
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
|
||||
type resolveIPAddrTest struct {
|
||||
net string
|
||||
litAddr string
|
||||
litAddrOrName string
|
||||
addr *IPAddr
|
||||
err error
|
||||
}
|
||||
@ -51,13 +51,20 @@ func init() {
|
||||
{"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)
|
||||
}
|
||||
|
@ -60,44 +60,61 @@ func (al addrList) toAddr() Addr {
|
||||
|
||||
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)
|
||||
}
|
||||
return addr, err
|
||||
} else {
|
||||
return firstSupportedAddr(filter, addrs, 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
|
||||
}
|
||||
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 @@ func JoinHostPort(host, port string) string {
|
||||
}
|
||||
|
||||
// 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 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
|
||||
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 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (netaddr, error)
|
||||
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 {
|
||||
|
189
src/pkg/net/ipsock_test.go
Normal file
189
src/pkg/net/ipsock_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -23,19 +23,19 @@ var protocols = map[string]int{
|
||||
|
||||
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 @@ func lookupHostMerge(host string) (addrs []string, err error) {
|
||||
// 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 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er
|
||||
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 @@ func lookupHostDeadline(host string, deadline time.Time) (addrs []string, err er
|
||||
// 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.
|
||||
|
@ -274,7 +274,7 @@ func benchmarkTCPConcurrentReadWrite(b *testing.B, laddr string) {
|
||||
|
||||
type resolveTCPAddrTest struct {
|
||||
net string
|
||||
litAddr string
|
||||
litAddrOrName string
|
||||
addr *TCPAddr
|
||||
err error
|
||||
}
|
||||
@ -303,13 +303,20 @@ func init() {
|
||||
{"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)
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
|
||||
type resolveUDPAddrTest struct {
|
||||
net string
|
||||
litAddr string
|
||||
litAddrOrName string
|
||||
addr *UDPAddr
|
||||
err error
|
||||
}
|
||||
@ -42,13 +42,20 @@ func init() {
|
||||
{"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)
|
||||
|
Loading…
Reference in New Issue
Block a user