Commit 866e0145 authored by Russ Cox's avatar Russ Cox

net: apply tcp4/tcp6 restrictions to literals in ResolveTCPAddr

The restrictions were already being applied to the IP addresses
received from the host resolver. Apply the same restrictions to
literal IP addresses not passed to the host resolver.

For example, ResolveTCPAddr("tcp4", "[2001:db8::1]:http") used
to succeed and now does not (that's not an IPv4 address).

Perhaps a bit surprisingly,
ResolveTCPAddr("tcp4", "[::ffff:127.0.0.1]:http") succeeds,
behaving identically to ResolveTCPAddr("tcp4", "127.0.0.1:http"), and
ResolveTCPAddr("tcp6", "[::ffff:127.0.0.1]:http") fails,
behaving identically to ResolveTCPAddr("tcp6", "127.0.0.1:http").
Even so, it seems right to match (by reusing) the existing filtering
as applied to addresses resolved by the host C library.
If anyone can make a strong argument for changing the filtering
of IPv4-inside-IPv6 addresses, the fix can be applied to all
the code paths in a separate CL.

Fixes #14037.

Change-Id: I690dfdcbe93d730e11e00ea387fa7484cd524341
Reviewed-on: https://go-review.googlesource.com/32100
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
parent c4099c75
...@@ -230,7 +230,7 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string ...@@ -230,7 +230,7 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string
} }
} }
if len(naddrs) == 0 { if len(naddrs) == 0 {
return nil, errNoSuitableAddress return nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: hint.String()}
} }
return naddrs, nil return naddrs, nil
} }
......
...@@ -230,23 +230,27 @@ func TestDialAddrError(t *testing.T) { ...@@ -230,23 +230,27 @@ func TestDialAddrError(t *testing.T) {
} { } {
var err error var err error
var c Conn var c Conn
var op string
if tt.lit != "" { if tt.lit != "" {
c, err = Dial(tt.network, JoinHostPort(tt.lit, "0")) c, err = Dial(tt.network, JoinHostPort(tt.lit, "0"))
op = fmt.Sprintf("Dial(%q, %q)", tt.network, JoinHostPort(tt.lit, "0"))
} else { } else {
c, err = DialTCP(tt.network, nil, tt.addr) c, err = DialTCP(tt.network, nil, tt.addr)
op = fmt.Sprintf("DialTCP(%q, %q)", tt.network, tt.addr)
} }
if err == nil { if err == nil {
c.Close() c.Close()
t.Errorf("%s %q/%v: should fail", tt.network, tt.lit, tt.addr) t.Errorf("%s succeeded, want error", op)
continue continue
} }
if perr := parseDialError(err); perr != nil { if perr := parseDialError(err); perr != nil {
t.Error(perr) t.Errorf("%s: %v", op, perr)
continue continue
} }
aerr, ok := err.(*OpError).Err.(*AddrError) operr := err.(*OpError).Err
aerr, ok := operr.(*AddrError)
if !ok { if !ok {
t.Errorf("%s %q/%v: should be AddrError: %v", tt.network, tt.lit, tt.addr, err) t.Errorf("%s: %v is %#T, want *AddrError", op, err, operr)
continue continue
} }
want := tt.lit want := tt.lit
...@@ -254,7 +258,7 @@ func TestDialAddrError(t *testing.T) { ...@@ -254,7 +258,7 @@ func TestDialAddrError(t *testing.T) {
want = tt.addr.IP.String() want = tt.addr.IP.String()
} }
if aerr.Addr != want { if aerr.Addr != want {
t.Fatalf("%s: got %q; want %q", tt.network, aerr.Addr, want) t.Errorf("%s: %v, error Addr=%q, want %q", op, err, aerr.Addr, want)
} }
} }
} }
......
...@@ -76,7 +76,7 @@ func (addrs addrList) partition(strategy func(Addr) bool) (primaries, fallbacks ...@@ -76,7 +76,7 @@ func (addrs addrList) partition(strategy func(Addr) bool) (primaries, fallbacks
// yielding a list of Addr objects. Known filters are nil, ipv4only, // yielding a list of Addr objects. Known filters are nil, ipv4only,
// and ipv6only. It returns every address when the filter is nil. // and ipv6only. It returns every address when the filter is nil.
// The result contains at least one address when error is nil. // The result contains at least one address when error is nil.
func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) Addr) (addrList, error) { func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr) Addr, originalAddr string) (addrList, error) {
var addrs addrList var addrs addrList
for _, ip := range ips { for _, ip := range ips {
if filter == nil || filter(ip) { if filter == nil || filter(ip) {
...@@ -84,21 +84,19 @@ func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr ...@@ -84,21 +84,19 @@ func filterAddrList(filter func(IPAddr) bool, ips []IPAddr, inetaddr func(IPAddr
} }
} }
if len(addrs) == 0 { if len(addrs) == 0 {
return nil, errNoSuitableAddress return nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: originalAddr}
} }
return addrs, nil return addrs, nil
} }
// ipv4only reports whether the kernel supports IPv4 addressing mode // ipv4only reports whether addr is an IPv4 address.
// and addr is an IPv4 address.
func ipv4only(addr IPAddr) bool { func ipv4only(addr IPAddr) bool {
return supportsIPv4 && addr.IP.To4() != nil return addr.IP.To4() != nil
} }
// ipv6only reports whether the kernel supports IPv6 addressing mode // ipv6only reports whether addr is an IPv6 address except IPv4-mapped IPv6 address.
// and addr is an IPv6 address except IPv4-mapped IPv6 address.
func ipv6only(addr IPAddr) bool { func ipv6only(addr IPAddr) bool {
return supportsIPv6 && len(addr.IP) == IPv6len && addr.IP.To4() == nil return len(addr.IP) == IPv6len && addr.IP.To4() == nil
} }
// SplitHostPort splits a network address of the form "host:port", // SplitHostPort splits a network address of the form "host:port",
...@@ -228,20 +226,21 @@ func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addr ...@@ -228,20 +226,21 @@ func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addr
if host == "" { if host == "" {
return addrList{inetaddr(IPAddr{})}, nil return addrList{inetaddr(IPAddr{})}, nil
} }
// Try as a literal IP address.
var ip IP // Try as a literal IP address, then as a DNS name.
if ip = parseIPv4(host); ip != nil { var ips []IPAddr
return addrList{inetaddr(IPAddr{IP: ip})}, nil if ip := parseIPv4(host); ip != nil {
} ips = []IPAddr{{IP: ip}}
var zone string } else if ip, zone := parseIPv6(host, true); ip != nil {
if ip, zone = parseIPv6(host, true); ip != nil { ips = []IPAddr{{IP: ip, Zone: zone}}
return addrList{inetaddr(IPAddr{IP: ip, Zone: zone})}, nil } else {
}
// Try as a DNS name. // Try as a DNS name.
ips, err := r.LookupIPAddr(ctx, host) ips, err = r.LookupIPAddr(ctx, host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
var filter func(IPAddr) bool var filter func(IPAddr) bool
if net != "" && net[len(net)-1] == '4' { if net != "" && net[len(net)-1] == '4' {
filter = ipv4only filter = ipv4only
...@@ -249,7 +248,7 @@ func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addr ...@@ -249,7 +248,7 @@ func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addr
if net != "" && net[len(net)-1] == '6' { if net != "" && net[len(net)-1] == '6' {
filter = ipv6only filter = ipv6only
} }
return filterAddrList(filter, ips, inetaddr) return filterAddrList(filter, ips, inetaddr, host)
} }
func loopbackIP(net string) IP { func loopbackIP(net string) IP {
......
...@@ -205,13 +205,13 @@ var addrListTests = []struct { ...@@ -205,13 +205,13 @@ var addrListTests = []struct {
nil, nil,
}, },
{nil, nil, testInetaddr, nil, nil, nil, errNoSuitableAddress}, {nil, nil, testInetaddr, nil, nil, nil, &AddrError{errNoSuitableAddress.Error(), "ADDR"}},
{ipv4only, nil, testInetaddr, nil, nil, nil, errNoSuitableAddress}, {ipv4only, nil, testInetaddr, nil, nil, nil, &AddrError{errNoSuitableAddress.Error(), "ADDR"}},
{ipv4only, []IPAddr{{IP: IPv6loopback}}, testInetaddr, nil, nil, nil, errNoSuitableAddress}, {ipv4only, []IPAddr{{IP: IPv6loopback}}, testInetaddr, nil, nil, nil, &AddrError{errNoSuitableAddress.Error(), "ADDR"}},
{ipv6only, nil, testInetaddr, nil, nil, nil, errNoSuitableAddress}, {ipv6only, nil, testInetaddr, nil, nil, nil, &AddrError{errNoSuitableAddress.Error(), "ADDR"}},
{ipv6only, []IPAddr{{IP: IPv4(127, 0, 0, 1)}}, testInetaddr, nil, nil, nil, errNoSuitableAddress}, {ipv6only, []IPAddr{{IP: IPv4(127, 0, 0, 1)}}, testInetaddr, nil, nil, nil, &AddrError{errNoSuitableAddress.Error(), "ADDR"}},
} }
func TestAddrList(t *testing.T) { func TestAddrList(t *testing.T) {
...@@ -220,8 +220,8 @@ func TestAddrList(t *testing.T) { ...@@ -220,8 +220,8 @@ func TestAddrList(t *testing.T) {
} }
for i, tt := range addrListTests { for i, tt := range addrListTests {
addrs, err := filterAddrList(tt.filter, tt.ips, tt.inetaddr) addrs, err := filterAddrList(tt.filter, tt.ips, tt.inetaddr, "ADDR")
if err != tt.err { if !reflect.DeepEqual(err, tt.err) {
t.Errorf("#%v: got %v; want %v", i, err, tt.err) t.Errorf("#%v: got %v; want %v", i, err, tt.err)
} }
if tt.err != nil { if tt.err != nil {
......
...@@ -519,7 +519,7 @@ func (e *AddrError) Error() string { ...@@ -519,7 +519,7 @@ func (e *AddrError) Error() string {
} }
s := e.Err s := e.Err
if e.Addr != "" { if e.Addr != "" {
s += " " + e.Addr s = "address " + e.Addr + ": " + s
} }
return s return s
} }
......
...@@ -311,6 +311,16 @@ var resolveTCPAddrTests = []resolveTCPAddrTest{ ...@@ -311,6 +311,16 @@ var resolveTCPAddrTests = []resolveTCPAddrTest{
{"tcp", ":12345", &TCPAddr{Port: 12345}, nil}, {"tcp", ":12345", &TCPAddr{Port: 12345}, nil},
{"http", "127.0.0.1:0", nil, UnknownNetworkError("http")}, {"http", "127.0.0.1:0", nil, UnknownNetworkError("http")},
{"tcp", "127.0.0.1:http", &TCPAddr{IP: ParseIP("127.0.0.1"), Port: 80}, nil},
{"tcp", "[::ffff:127.0.0.1]:http", &TCPAddr{IP: ParseIP("::ffff:127.0.0.1"), Port: 80}, nil},
{"tcp", "[2001:db8::1]:http", &TCPAddr{IP: ParseIP("2001:db8::1"), Port: 80}, nil},
{"tcp4", "127.0.0.1:http", &TCPAddr{IP: ParseIP("127.0.0.1"), Port: 80}, nil},
{"tcp4", "[::ffff:127.0.0.1]:http", &TCPAddr{IP: ParseIP("127.0.0.1"), Port: 80}, nil},
{"tcp4", "[2001:db8::1]:http", nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: "2001:db8::1"}},
{"tcp6", "127.0.0.1:http", nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: "127.0.0.1"}},
{"tcp6", "[::ffff:127.0.0.1]:http", nil, &AddrError{Err: errNoSuitableAddress.Error(), Addr: "::ffff:127.0.0.1"}},
{"tcp6", "[2001:db8::1]:http", &TCPAddr{IP: ParseIP("2001:db8::1"), Port: 80}, nil},
} }
func TestResolveTCPAddr(t *testing.T) { func TestResolveTCPAddr(t *testing.T) {
...@@ -318,21 +328,17 @@ func TestResolveTCPAddr(t *testing.T) { ...@@ -318,21 +328,17 @@ func TestResolveTCPAddr(t *testing.T) {
defer func() { testHookLookupIP = origTestHookLookupIP }() defer func() { testHookLookupIP = origTestHookLookupIP }()
testHookLookupIP = lookupLocalhost testHookLookupIP = lookupLocalhost
for i, tt := range resolveTCPAddrTests { for _, tt := range resolveTCPAddrTests {
addr, err := ResolveTCPAddr(tt.network, tt.litAddrOrName) addr, err := ResolveTCPAddr(tt.network, tt.litAddrOrName)
if err != tt.err { if !reflect.DeepEqual(addr, tt.addr) || !reflect.DeepEqual(err, tt.err) {
t.Errorf("#%d: %v", i, err) t.Errorf("ResolveTCPAddr(%q, %q) = %v, %v, want %v, %v", tt.network, tt.litAddrOrName, addr, err, tt.addr, tt.err)
} else if !reflect.DeepEqual(addr, tt.addr) {
t.Errorf("#%d: got %#v; want %#v", i, addr, tt.addr)
}
if err != nil {
continue continue
} }
rtaddr, err := ResolveTCPAddr(addr.Network(), addr.String()) if err == nil {
if err != nil { addr2, err := ResolveTCPAddr(addr.Network(), addr.String())
t.Errorf("#%d: %v", i, err) if !reflect.DeepEqual(addr2, tt.addr) || err != tt.err {
} else if !reflect.DeepEqual(rtaddr, addr) { t.Errorf("(%q, %q): ResolveTCPAddr(%q, %q) = %v, %v, want %v, %v", tt.network, tt.litAddrOrName, addr.Network(), addr.String(), addr2, err, tt.addr, tt.err)
t.Errorf("#%d: got %#v; want %#v", i, rtaddr, 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