net: support IPv6 scoped addressing zone

This CL provides IPv6 scoped addressing zone support as defined
in RFC 4007 for internet protocol family connection setups.

Follwoing types and functions allow a literal IPv6 address with
zone identifer as theirs parameter.

pkg net, func Dial(string, string) (Conn, error)
pkg net, func DialOpt(string, ...DialOption) (Conn, error)
pkg net, func DialTimeout(string, string, time.Duration) (Conn, error)
pkg net, func Listen(string, string) (Listener, error)
pkg net, func ListenPacket(string, string) (PacketConn, error)
pkg net, func ResolveIPAddr(string, string) (*IPAddr, error)
pkg net, func ResolveTCPAddr(string, string) (*TCPAddr, error)
pkg net, func ResolveUDPAddr(string, string) (*UDPAddr, error)
pkg net, type IPAddr struct, Zone string
pkg net, type TCPAddr struct, Zone string
pkg net, type UDPAddr struct, Zone string

Also follwoing methods return a literal IPv6 address with zone
identifier string if possible.

pkg net, method (*IPAddr) String() string
pkg net, method (*TCPAddr) String() string
pkg net, method (*UDPAddr) String() string

Fixes #4234.
Fixes #4501.
Update #5081.

R=rsc, iant
CC=golang-dev
https://golang.org/cl/6816116
This commit is contained in:
Mikio Hara 2013-03-23 09:57:40 +09:00
parent c90850277b
commit aa0dda767a
11 changed files with 395 additions and 105 deletions

View file

@ -169,22 +169,27 @@ func resolveAddr(op, net, addr string, deadline time.Time) (Addr, error) {
// "unixpacket". // "unixpacket".
// //
// For TCP and UDP networks, addresses have the form host:port. // For TCP and UDP networks, addresses have the form host:port.
// If host is a literal IPv6 address, it must be enclosed // If host is a literal IPv6 address or host name, it must be enclosed
// in square brackets. The functions JoinHostPort and SplitHostPort // in square brackets as in "[::1]:80", "[ipv6-host]:http" or
// manipulate addresses in this form. // "[ipv6-host%zone]:80".
// The functions JoinHostPort and SplitHostPort manipulate addresses
// in this form.
// //
// Examples: // Examples:
// Dial("tcp", "12.34.56.78:80") // Dial("tcp", "12.34.56.78:80")
// Dial("tcp", "google.com:80") // Dial("tcp", "google.com:http")
// Dial("tcp", "[de:ad:be:ef::ca:fe]:80") // Dial("tcp", "[2001:db8::1]:http")
// Dial("tcp", "[fe80::1%lo0]:80")
// //
// For IP networks, net must be "ip", "ip4" or "ip6" followed // For IP networks, the net must be "ip", "ip4" or "ip6" followed by a
// by a colon and a protocol number or name. // colon and a protocol number or name and the addr must be a literal
// IP address.
// //
// Examples: // Examples:
// Dial("ip4:1", "127.0.0.1") // Dial("ip4:1", "127.0.0.1")
// Dial("ip6:ospf", "::1") // Dial("ip6:ospf", "::1")
// //
// For Unix networks, the addr must be a file system path.
func Dial(net, addr string) (Conn, error) { func Dial(net, addr string) (Conn, error) {
return DialOpt(addr, dialNetwork(net)) return DialOpt(addr, dialNetwork(net))
} }
@ -290,8 +295,9 @@ func (a stringAddr) Network() string { return a.net }
func (a stringAddr) String() string { return a.addr } func (a stringAddr) String() string { return a.addr }
// Listen announces on the local network address laddr. // Listen announces on the local network address laddr.
// The network string net must be a stream-oriented network: // The network net must be a stream-oriented network: "tcp", "tcp4",
// "tcp", "tcp4", "tcp6", "unix" or "unixpacket". // "tcp6", "unix" or "unixpacket".
// See Dial for the syntax of laddr.
func Listen(net, laddr string) (Listener, error) { func Listen(net, laddr string) (Listener, error) {
la, err := resolveAddr("listen", net, laddr, noDeadline) la, err := resolveAddr("listen", net, laddr, noDeadline)
if err != nil { if err != nil {
@ -307,8 +313,9 @@ func Listen(net, laddr string) (Listener, error) {
} }
// ListenPacket announces on the local network address laddr. // ListenPacket announces on the local network address laddr.
// The network string net must be a packet-oriented network: // The network net must be a packet-oriented network: "udp", "udp4",
// "udp", "udp4", "udp6", "ip", "ip4", "ip6" or "unixgram". // "udp6", "ip", "ip4", "ip6" or "unixgram".
// See Dial for the syntax of laddr.
func ListenPacket(net, laddr string) (PacketConn, error) { func ListenPacket(net, laddr string) (PacketConn, error) {
la, err := resolveAddr("listen", net, laddr, noDeadline) la, err := resolveAddr("listen", net, laddr, noDeadline)
if err != nil { if err != nil {

View file

@ -25,6 +25,32 @@ func loopbackInterface() *Interface {
return nil return nil
} }
// ipv6LinkLocalUnicastAddr returns an IPv6 link-local unicast address
// on the given network interface for tests. It returns "" if no
// suitable address is found.
func ipv6LinkLocalUnicastAddr(ifi *Interface) string {
if ifi == nil {
return ""
}
ifat, err := ifi.Addrs()
if err != nil {
return ""
}
for _, ifa := range ifat {
switch ifa := ifa.(type) {
case *IPAddr:
if ifa.IP.To4() == nil && ifa.IP.IsLinkLocalUnicast() {
return ifa.IP.String()
}
case *IPNet:
if ifa.IP.To4() == nil && ifa.IP.IsLinkLocalUnicast() {
return ifa.IP.String()
}
}
}
return ""
}
func TestInterfaces(t *testing.T) { func TestInterfaces(t *testing.T) {
ift, err := Interfaces() ift, err := Interfaces()
if err != nil { if err != nil {
@ -81,9 +107,9 @@ func testInterfaceMulticastAddrs(t *testing.T, ifi *Interface) {
func testAddrs(t *testing.T, ifat []Addr) { func testAddrs(t *testing.T, ifat []Addr) {
for _, ifa := range ifat { for _, ifa := range ifat {
switch v := ifa.(type) { switch ifa := ifa.(type) {
case *IPAddr, *IPNet: case *IPAddr, *IPNet:
if v == nil { if ifa == nil {
t.Errorf("\tunexpected value: %v", ifa) t.Errorf("\tunexpected value: %v", ifa)
} else { } else {
t.Logf("\tinterface address %q", ifa.String()) t.Logf("\tinterface address %q", ifa.String())
@ -96,9 +122,9 @@ func testAddrs(t *testing.T, ifat []Addr) {
func testMulticastAddrs(t *testing.T, ifmat []Addr) { func testMulticastAddrs(t *testing.T, ifmat []Addr) {
for _, ifma := range ifmat { for _, ifma := range ifmat {
switch v := ifma.(type) { switch ifma := ifma.(type) {
case *IPAddr: case *IPAddr:
if v == nil { if ifma == nil {
t.Errorf("\tunexpected value: %v", ifma) t.Errorf("\tunexpected value: %v", ifma)
} else { } else {
t.Logf("\tjoined group address %q", ifma.String()) t.Logf("\tjoined group address %q", ifma.String())

View file

@ -431,6 +431,9 @@ func (n *IPNet) Contains(ip IP) bool {
return true return true
} }
// Network returns the address's network name, "ip+net".
func (n *IPNet) Network() string { return "ip+net" }
// String returns the CIDR notation of n like "192.168.100.1/24" // String returns the CIDR notation of n like "192.168.100.1/24"
// or "2001:DB8::/48" as defined in RFC 4632 and RFC 4291. // or "2001:DB8::/48" as defined in RFC 4632 and RFC 4291.
// If the mask is not in the canonical form, it returns the // If the mask is not in the canonical form, it returns the
@ -449,9 +452,6 @@ func (n *IPNet) String() string {
return nn.String() + "/" + itod(uint(l)) return nn.String() + "/" + itod(uint(l))
} }
// Network returns the address's network name, "ip+net".
func (n *IPNet) Network() string { return "ip+net" }
// Parse IPv4 address (d.d.d.d). // Parse IPv4 address (d.d.d.d).
func parseIPv4(s string) IP { func parseIPv4(s string) IP {
var p [IPv4len]byte var p [IPv4len]byte
@ -483,26 +483,26 @@ func parseIPv4(s string) IP {
return IPv4(p[0], p[1], p[2], p[3]) return IPv4(p[0], p[1], p[2], p[3])
} }
// Parse IPv6 address. Many forms. // parseIPv6 parses s as a literal IPv6 address described in RFC 4291
// The basic form is a sequence of eight colon-separated // and RFC 5952. It can also parse a literal scoped IPv6 address with
// 16-bit hex numbers separated by colons, // zone identifier which is described in RFC 4007 when zoneAllowed is
// as in 0123:4567:89ab:cdef:0123:4567:89ab:cdef. // true.
// Two exceptions: func parseIPv6(s string, zoneAllowed bool) (ip IP, zone string) {
// * A run of zeros can be replaced with "::". ip = make(IP, IPv6len)
// * The last 32 bits can be in IPv4 form.
// Thus, ::ffff:1.2.3.4 is the IPv4 address 1.2.3.4.
func parseIPv6(s string) IP {
p := make(IP, IPv6len)
ellipsis := -1 // position of ellipsis in p ellipsis := -1 // position of ellipsis in p
i := 0 // index in string s i := 0 // index in string s
if zoneAllowed {
s, zone = splitHostZone(s)
}
// Might have leading ellipsis // Might have leading ellipsis
if len(s) >= 2 && s[0] == ':' && s[1] == ':' { if len(s) >= 2 && s[0] == ':' && s[1] == ':' {
ellipsis = 0 ellipsis = 0
i = 2 i = 2
// Might be only ellipsis // Might be only ellipsis
if i == len(s) { if i == len(s) {
return p return ip, zone
} }
} }
@ -512,35 +512,35 @@ func parseIPv6(s string) IP {
// Hex number. // Hex number.
n, i1, ok := xtoi(s, i) n, i1, ok := xtoi(s, i)
if !ok || n > 0xFFFF { if !ok || n > 0xFFFF {
return nil return nil, zone
} }
// If followed by dot, might be in trailing IPv4. // If followed by dot, might be in trailing IPv4.
if i1 < len(s) && s[i1] == '.' { if i1 < len(s) && s[i1] == '.' {
if ellipsis < 0 && j != IPv6len-IPv4len { if ellipsis < 0 && j != IPv6len-IPv4len {
// Not the right place. // Not the right place.
return nil return nil, zone
} }
if j+IPv4len > IPv6len { if j+IPv4len > IPv6len {
// Not enough room. // Not enough room.
return nil return nil, zone
} }
p4 := parseIPv4(s[i:]) ip4 := parseIPv4(s[i:])
if p4 == nil { if ip4 == nil {
return nil return nil, zone
} }
p[j] = p4[12] ip[j] = ip4[12]
p[j+1] = p4[13] ip[j+1] = ip4[13]
p[j+2] = p4[14] ip[j+2] = ip4[14]
p[j+3] = p4[15] ip[j+3] = ip4[15]
i = len(s) i = len(s)
j += IPv4len j += IPv4len
break break
} }
// Save this 16-bit chunk. // Save this 16-bit chunk.
p[j] = byte(n >> 8) ip[j] = byte(n >> 8)
p[j+1] = byte(n) ip[j+1] = byte(n)
j += 2 j += 2
// Stop at end of string. // Stop at end of string.
@ -551,14 +551,14 @@ func parseIPv6(s string) IP {
// Otherwise must be followed by colon and more. // Otherwise must be followed by colon and more.
if s[i] != ':' || i+1 == len(s) { if s[i] != ':' || i+1 == len(s) {
return nil return nil, zone
} }
i++ i++
// Look for ellipsis. // Look for ellipsis.
if s[i] == ':' { if s[i] == ':' {
if ellipsis >= 0 { // already have one if ellipsis >= 0 { // already have one
return nil return nil, zone
} }
ellipsis = j ellipsis = j
if i++; i == len(s) { // can be at end if i++; i == len(s) { // can be at end
@ -569,23 +569,23 @@ func parseIPv6(s string) IP {
// Must have used entire string. // Must have used entire string.
if i != len(s) { if i != len(s) {
return nil return nil, zone
} }
// If didn't parse enough, expand ellipsis. // If didn't parse enough, expand ellipsis.
if j < IPv6len { if j < IPv6len {
if ellipsis < 0 { if ellipsis < 0 {
return nil return nil, zone
} }
n := IPv6len - j n := IPv6len - j
for k := j - 1; k >= ellipsis; k-- { for k := j - 1; k >= ellipsis; k-- {
p[k+n] = p[k] ip[k+n] = ip[k]
} }
for k := ellipsis + n - 1; k >= ellipsis; k-- { for k := ellipsis + n - 1; k >= ellipsis; k-- {
p[k] = 0 ip[k] = 0
} }
} }
return p return ip, zone
} }
// A ParseError represents a malformed text string and the type of string that was expected. // A ParseError represents a malformed text string and the type of string that was expected.
@ -598,26 +598,17 @@ func (e *ParseError) Error() string {
return "invalid " + e.Type + ": " + e.Text return "invalid " + e.Type + ": " + e.Text
} }
func parseIP(s string) IP {
if p := parseIPv4(s); p != nil {
return p
}
if p := parseIPv6(s); p != nil {
return p
}
return nil
}
// ParseIP parses s as an IP address, returning the result. // ParseIP parses s as an IP address, returning the result.
// The string s can be in dotted decimal ("74.125.19.99") // The string s can be in dotted decimal ("74.125.19.99")
// or IPv6 ("2001:4860:0:2001::68") form. // or IPv6 ("2001:4860:0:2001::68") form.
// If s is not a valid textual representation of an IP address, // If s is not a valid textual representation of an IP address,
// ParseIP returns nil. // ParseIP returns nil.
func ParseIP(s string) IP { func ParseIP(s string) IP {
if p := parseIPv4(s); p != nil { if ip := parseIPv4(s); ip != nil {
return p return ip
} }
return parseIPv6(s) ip, _ := parseIPv6(s, false)
return ip
} }
// ParseCIDR parses s as a CIDR notation IP address and mask, // ParseCIDR parses s as a CIDR notation IP address and mask,
@ -632,15 +623,15 @@ func ParseCIDR(s string) (IP, *IPNet, error) {
if i < 0 { if i < 0 {
return nil, nil, &ParseError{"CIDR address", s} return nil, nil, &ParseError{"CIDR address", s}
} }
ipstr, maskstr := s[:i], s[i+1:] addr, mask := s[:i], s[i+1:]
iplen := IPv4len iplen := IPv4len
ip := parseIPv4(ipstr) ip := parseIPv4(addr)
if ip == nil { if ip == nil {
iplen = IPv6len iplen = IPv6len
ip = parseIPv6(ipstr) ip, _ = parseIPv6(addr, false)
} }
n, i, ok := dtoi(maskstr, 0) n, i, ok := dtoi(mask, 0)
if ip == nil || !ok || i != len(maskstr) || n < 0 || n > 8*iplen { if ip == nil || !ok || i != len(mask) || n < 0 || n > 8*iplen {
return nil, nil, &ParseError{"CIDR address", s} return nil, nil, &ParseError{"CIDR address", s}
} }
m := CIDRMask(n, 8*iplen) m := CIDRMask(n, 8*iplen)

View file

@ -22,6 +22,8 @@ var parseIPTests = []struct {
{"::ffff:127.0.0.1", IPv4(127, 0, 0, 1)}, {"::ffff:127.0.0.1", IPv4(127, 0, 0, 1)},
{"2001:4860:0:2001::68", IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}}, {"2001:4860:0:2001::68", IP{0x20, 0x01, 0x48, 0x60, 0, 0, 0x20, 0x01, 0, 0, 0, 0, 0, 0, 0x00, 0x68}},
{"::ffff:4a7d:1363", IPv4(74, 125, 19, 99)}, {"::ffff:4a7d:1363", IPv4(74, 125, 19, 99)},
{"fe80::1%lo0", nil},
{"fe80::1%911", nil},
{"", nil}, {"", nil},
} }
@ -37,7 +39,6 @@ var ipStringTests = []struct {
in IP in IP
out string // see RFC 5952 out string // see RFC 5952
}{ }{
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0x1, 0x23, 0, 0x12, 0, 0x1}, "2001:db8::123:12:1"}, {IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0x1, 0x23, 0, 0x12, 0, 0x1}, "2001:db8::123:12:1"},
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1}, "2001:db8::1"}, {IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1}, "2001:db8::1"},
{IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0x1, 0, 0, 0, 0x1, 0, 0, 0, 0x1}, "2001:db8:0:1:0:1:0:1"}, {IP{0x20, 0x1, 0xd, 0xb8, 0, 0, 0, 0x1, 0, 0, 0, 0x1, 0, 0, 0, 0x1}, "2001:db8:0:1:0:1:0:1"},
@ -257,8 +258,11 @@ var splitJoinTests = []struct {
{"www.google.com", "80", "www.google.com:80"}, {"www.google.com", "80", "www.google.com:80"},
{"127.0.0.1", "1234", "127.0.0.1:1234"}, {"127.0.0.1", "1234", "127.0.0.1:1234"},
{"::1", "80", "[::1]:80"}, {"::1", "80", "[::1]:80"},
{"google.com", "https%foo", "google.com:https%foo"}, // Go 1.0 behavior {"fe80::1%lo0", "80", "[fe80::1%lo0]:80"},
{"localhost%lo0", "80", "[localhost%lo0]:80"},
{"", "0", ":0"}, {"", "0", ":0"},
{"google.com", "https%foo", "google.com:https%foo"}, // Go 1.0 behavior
{"127.0.0.1", "", "127.0.0.1:"}, // Go 1.0 behaviour {"127.0.0.1", "", "127.0.0.1:"}, // Go 1.0 behaviour
{"www.google.com", "", "www.google.com:"}, // Go 1.0 behaviour {"www.google.com", "", "www.google.com:"}, // Go 1.0 behaviour
} }
@ -270,15 +274,27 @@ var splitFailureTests = []struct {
{"www.google.com", "missing port in address"}, {"www.google.com", "missing port in address"},
{"127.0.0.1", "missing port in address"}, {"127.0.0.1", "missing port in address"},
{"[::1]", "missing port in address"}, {"[::1]", "missing port in address"},
{"[fe80::1%lo0]", "missing port in address"},
{"[localhost%lo0]", "missing port in address"},
{"localhost%lo0", "missing port in address"},
{"::1", "too many colons in address"}, {"::1", "too many colons in address"},
{"fe80::1%lo0", "too many colons in address"},
{"fe80::1%lo0:80", "too many colons in address"},
{"localhost%lo0:80", "missing brackets in address"},
// Test cases that didn't fail in Go 1.0 // Test cases that didn't fail in Go 1.0
{"[foo:bar]", "missing port in address"}, {"[foo:bar]", "missing port in address"},
{"[foo:bar]baz", "missing port in address"}, {"[foo:bar]baz", "missing port in address"},
{"[foo]:[bar]:baz", "too many colons in address"},
{"[foo]bar:baz", "missing port in address"}, {"[foo]bar:baz", "missing port in address"},
{"[foo]:[bar]:baz", "too many colons in address"},
{"[foo]:[bar]baz", "unexpected '[' in address"}, {"[foo]:[bar]baz", "unexpected '[' in address"},
{"foo[bar]:baz", "unexpected '[' in address"}, {"foo[bar]:baz", "unexpected '[' in address"},
{"foo]bar:baz", "unexpected ']' in address"}, {"foo]bar:baz", "unexpected ']' in address"},
} }

View file

@ -7,6 +7,7 @@ package net
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"os" "os"
"reflect" "reflect"
"testing" "testing"
@ -27,6 +28,11 @@ var resolveIPAddrTests = []struct {
{"ip6", "::1", &IPAddr{IP: ParseIP("::1")}, nil}, {"ip6", "::1", &IPAddr{IP: ParseIP("::1")}, nil},
{"ip6:icmp", "::1", &IPAddr{IP: ParseIP("::1")}, nil}, {"ip6:icmp", "::1", &IPAddr{IP: ParseIP("::1")}, nil},
{"ip", "::1%en0", &IPAddr{IP: ParseIP("::1"), Zone: "en0"}, nil},
{"ip6", "::1%911", &IPAddr{IP: ParseIP("::1"), Zone: "911"}, nil},
{"ip6", "fe80::1", &IPAddr{IP: ParseIP("fe80::1"), Zone: "name"}, nil},
{"ip6", "fe80::1", &IPAddr{IP: ParseIP("fe80::1"), Zone: "index"}, nil},
{"", "127.0.0.1", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil}, // Go 1.0 behavior {"", "127.0.0.1", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil}, // Go 1.0 behavior
{"", "::1", &IPAddr{IP: ParseIP("::1")}, nil}, // Go 1.0 behavior {"", "::1", &IPAddr{IP: ParseIP("::1")}, nil}, // Go 1.0 behavior
@ -37,6 +43,21 @@ var resolveIPAddrTests = []struct {
func TestResolveIPAddr(t *testing.T) { func TestResolveIPAddr(t *testing.T) {
for _, tt := range resolveIPAddrTests { for _, tt := range resolveIPAddrTests {
if tt.addr != nil && (tt.addr.Zone == "name" || tt.addr.Zone == "index") {
ifi := loopbackInterface()
if ifi == nil {
continue
}
switch tt.addr.Zone {
case "name":
tt.litAddr += "%" + ifi.Name
tt.addr.Zone = zoneToString(ifi.Index)
case "index":
index := fmt.Sprintf("%v", ifi.Index)
tt.litAddr += "%" + index
tt.addr.Zone = index
}
}
addr, err := ResolveIPAddr(tt.net, tt.litAddr) addr, err := ResolveIPAddr(tt.net, tt.litAddr)
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.litAddr, err)

View file

@ -19,12 +19,15 @@ func (a *IPAddr) String() string {
if a == nil { if a == nil {
return "<nil>" return "<nil>"
} }
if a.Zone != "" {
return a.IP.String() + "%" + a.Zone
}
return a.IP.String() return a.IP.String()
} }
// ResolveIPAddr parses addr as an IP address and resolves domain // ResolveIPAddr parses addr as an IP address of the form "host" or
// names to numeric addresses on the network net, which must be // "ipv6-host%zone" and resolves the domain name on the network net,
// "ip", "ip4" or "ip6". // which must be "ip", "ip4" or "ip6".
func ResolveIPAddr(net, addr string) (*IPAddr, error) { func ResolveIPAddr(net, addr string) (*IPAddr, error) {
if net == "" { // a hint wildcard for Go 1.0 undocumented behavior if net == "" { // a hint wildcard for Go 1.0 undocumented behavior
net = "ip" net = "ip"

View file

@ -68,15 +68,12 @@ func (e InvalidAddrError) Error() string { return string(e) }
func (e InvalidAddrError) Timeout() bool { return false } func (e InvalidAddrError) Timeout() bool { return false }
func (e InvalidAddrError) Temporary() bool { return false } func (e InvalidAddrError) Temporary() bool { return false }
// SplitHostPort splits a network address of the form // SplitHostPort splits a network address of the form "host:port",
// "host:port" or "[host]:port" into host and port. // "[host]:port" or "[ipv6-host%zone]:port" into host or
// The latter form must be used when host contains a colon. // ipv6-host%zone and port. A literal address or host name for IPv6
// must be enclosed in square brackets, as in "[::1]:80",
// "[ipv6-host]:http" or "[ipv6-host%zone]:80".
func SplitHostPort(hostport string) (host, port string, err error) { func SplitHostPort(hostport string) (host, port string, err error) {
host, port, _, err = splitHostPort(hostport)
return
}
func splitHostPort(hostport string) (host, port, zone string, err error) {
j, k := 0, 0 j, k := 0, 0
// The port starts after the last colon. // The port starts after the last colon.
@ -110,10 +107,12 @@ func splitHostPort(hostport string) (host, port, zone string, err error) {
j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions
} else { } else {
host = hostport[:i] host = hostport[:i]
if byteIndex(host, ':') >= 0 { if byteIndex(host, ':') >= 0 {
goto tooManyColons goto tooManyColons
} }
if byteIndex(host, '%') >= 0 {
goto missingBrackets
}
} }
if byteIndex(hostport[j:], '[') >= 0 { if byteIndex(hostport[j:], '[') >= 0 {
err = &AddrError{"unexpected '[' in address", hostport} err = &AddrError{"unexpected '[' in address", hostport}
@ -134,13 +133,29 @@ missingPort:
tooManyColons: tooManyColons:
err = &AddrError{"too many colons in address", hostport} err = &AddrError{"too many colons in address", hostport}
return return
missingBrackets:
err = &AddrError{"missing brackets in address", hostport}
return
} }
// JoinHostPort combines host and port into a network address func splitHostZone(s string) (host, zone string) {
// of the form "host:port" or, if host contains a colon, "[host]:port". // The IPv6 scoped addressing zone identifer starts after the
// last percent sign.
if i := last(s, '%'); i > 0 {
host, zone = s[:i], s[i+1:]
} else {
host = s
}
return
}
// JoinHostPort combines host and port into a network address of the
// form "host:port" or, if host contains a colon or a percent sign,
// "[host]:port".
func JoinHostPort(host, port string) string { func JoinHostPort(host, port string) string {
// If host has colons, have to bracket it. // If host has colons or a percent sign, have to bracket it.
if byteIndex(host, ':') >= 0 { if byteIndex(host, ':') >= 0 || byteIndex(host, '%') >= 0 {
return "[" + host + "]:" + port return "[" + host + "]:" + port
} }
return host + ":" + port return host + ":" + port
@ -155,7 +170,7 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (Addr, error) {
switch net { switch net {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
if addr != "" { if addr != "" {
if host, port, zone, err = splitHostPort(addr); err != nil { if host, port, err = SplitHostPort(addr); err != nil {
return nil, err return nil, err
} }
if portnum, err = parsePort(net, port); err != nil { if portnum, err = parsePort(net, port); err != nil {
@ -184,21 +199,25 @@ func resolveInternetAddr(net, addr string, deadline time.Time) (Addr, error) {
return inetaddr(net, nil, portnum, zone), nil return inetaddr(net, nil, portnum, zone), nil
} }
// Try as an IP address. // Try as an IP address.
if ip := ParseIP(host); ip != nil { if ip := parseIPv4(host); ip != nil {
return inetaddr(net, ip, portnum, zone), nil return inetaddr(net, ip, portnum, zone), nil
} }
if ip, zone := parseIPv6(host, true); ip != nil {
return inetaddr(net, ip, portnum, zone), nil
}
// Try as a domain name.
host, zone = splitHostZone(host)
addrs, err := lookupHostDeadline(host, deadline)
if err != nil {
return nil, err
}
var filter func(IP) IP var filter func(IP) IP
if net != "" && net[len(net)-1] == '4' { if net != "" && net[len(net)-1] == '4' {
filter = ipv4only filter = ipv4only
} }
if net != "" && net[len(net)-1] == '6' { if net != "" && net[len(net)-1] == '6' || zone != "" {
filter = ipv6only filter = ipv6only
} }
// Try as a DNS name.
addrs, err := lookupHostDeadline(host, deadline)
if err != nil {
return nil, err
}
ip := firstFavoriteAddr(filter, addrs) ip := firstFavoriteAddr(filter, addrs)
if ip == nil { if ip == nil {
// should not happen // should not happen

View file

@ -5,6 +5,7 @@
package net package net
import ( import (
"fmt"
"reflect" "reflect"
"runtime" "runtime"
"testing" "testing"
@ -158,6 +159,11 @@ var resolveTCPAddrTests = []struct {
{"tcp", "[::1]:1", &TCPAddr{IP: ParseIP("::1"), Port: 1}, nil}, {"tcp", "[::1]:1", &TCPAddr{IP: ParseIP("::1"), Port: 1}, nil},
{"tcp6", "[::1]:65534", &TCPAddr{IP: ParseIP("::1"), Port: 65534}, nil}, {"tcp6", "[::1]:65534", &TCPAddr{IP: ParseIP("::1"), Port: 65534}, nil},
{"tcp", "[::1%en0]:1", &TCPAddr{IP: ParseIP("::1"), Port: 1, Zone: "en0"}, nil},
{"tcp6", "[::1%911]:2", &TCPAddr{IP: ParseIP("::1"), Port: 2, Zone: "911"}, nil},
{"tcp6", "[fe80::1]:3", &TCPAddr{IP: ParseIP("fe80::1"), Port: 3, Zone: "name"}, nil},
{"tcp6", "[fe80::1]:4", &TCPAddr{IP: ParseIP("fe80::1"), Port: 4, Zone: "index"}, nil},
{"", "127.0.0.1:0", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 0}, nil}, // Go 1.0 behavior {"", "127.0.0.1:0", &TCPAddr{IP: IPv4(127, 0, 0, 1), Port: 0}, nil}, // Go 1.0 behavior
{"", "[::1]:0", &TCPAddr{IP: ParseIP("::1"), Port: 0}, nil}, // Go 1.0 behavior {"", "[::1]:0", &TCPAddr{IP: ParseIP("::1"), Port: 0}, nil}, // Go 1.0 behavior
@ -166,6 +172,24 @@ var resolveTCPAddrTests = []struct {
func TestResolveTCPAddr(t *testing.T) { func TestResolveTCPAddr(t *testing.T) {
for _, tt := range resolveTCPAddrTests { for _, tt := range resolveTCPAddrTests {
if tt.addr != nil && (tt.addr.Zone == "name" || tt.addr.Zone == "index") {
ifi := loopbackInterface()
if ifi == nil {
continue
}
i := last(tt.litAddr, ']')
if i > 0 {
switch tt.addr.Zone {
case "name":
tt.litAddr = tt.litAddr[:i] + "%" + ifi.Name + tt.litAddr[i:]
tt.addr.Zone = zoneToString(ifi.Index)
case "index":
index := fmt.Sprintf("%v", ifi.Index)
tt.litAddr = tt.litAddr[:i] + "%" + index + tt.litAddr[i:]
tt.addr.Zone = index
}
}
}
addr, err := ResolveTCPAddr(tt.net, tt.litAddr) addr, err := ResolveTCPAddr(tt.net, tt.litAddr)
if err != tt.err { if err != tt.err {
t.Fatalf("ResolveTCPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err) t.Fatalf("ResolveTCPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
@ -204,3 +228,79 @@ func TestTCPListenerName(t *testing.T) {
} }
} }
} }
func TestIPv6LinkLocalUnicastTCP(t *testing.T) {
if testing.Short() || !*testExternal {
t.Skip("skipping test to avoid external network")
}
if !supportsIPv6 {
t.Skip("ipv6 is not supported")
}
ifi := loopbackInterface()
if ifi == nil {
t.Skip("loopback interface not found")
}
laddr := ipv6LinkLocalUnicastAddr(ifi)
if laddr == "" {
t.Skip("ipv6 unicast address on loopback not found")
}
type test struct {
net, addr string
nameLookup bool
}
var tests = []test{
{"tcp", "[" + laddr + "%" + ifi.Name + "]:0", false},
{"tcp6", "[" + laddr + "%" + ifi.Name + "]:0", false},
}
switch runtime.GOOS {
case "darwin", "freebsd", "opensbd", "netbsd":
tests = append(tests, []test{
{"tcp", "[localhost%" + ifi.Name + "]:0", true},
{"tcp6", "[localhost%" + ifi.Name + "]:0", true},
}...)
case "linux":
tests = append(tests, []test{
{"tcp", "[ip6-localhost%" + ifi.Name + "]:0", true},
{"tcp6", "[ip6-localhost%" + ifi.Name + "]:0", true},
}...)
}
for _, tt := range tests {
ln, err := Listen(tt.net, tt.addr)
if err != nil {
// It might return "LookupHost returned no
// suitable address" error on some platforms.
t.Logf("Listen failed: %v", err)
continue
}
defer ln.Close()
if la, ok := ln.Addr().(*TCPAddr); !ok || !tt.nameLookup && la.Zone == "" {
t.Fatalf("got %v; expected a proper address with zone identifier", la)
}
done := make(chan int)
go transponder(t, ln, done)
c, err := Dial(tt.net, ln.Addr().String())
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
defer c.Close()
if la, ok := c.LocalAddr().(*TCPAddr); !ok || !tt.nameLookup && la.Zone == "" {
t.Fatalf("got %v; expected a proper address with zone identifier", la)
}
if ra, ok := c.RemoteAddr().(*TCPAddr); !ok || !tt.nameLookup && ra.Zone == "" {
t.Fatalf("got %v; expected a proper address with zone identifier", ra)
}
if _, err := c.Write([]byte("TCP OVER IPV6 LINKLOCAL TEST")); err != nil {
t.Fatalf("Conn.Write failed: %v", err)
}
b := make([]byte, 32)
if _, err := c.Read(b); err != nil {
t.Fatalf("Conn.Read failed: %v", err)
}
<-done
}
}

View file

@ -20,14 +20,18 @@ func (a *TCPAddr) String() string {
if a == nil { if a == nil {
return "<nil>" return "<nil>"
} }
if a.Zone != "" {
return JoinHostPort(a.IP.String()+"%"+a.Zone, itoa(a.Port))
}
return JoinHostPort(a.IP.String(), itoa(a.Port)) return JoinHostPort(a.IP.String(), itoa(a.Port))
} }
// ResolveTCPAddr parses addr as a TCP address of the form // ResolveTCPAddr parses addr as a TCP address of the form "host:port"
// host:port and resolves domain names or port names to // or "[ipv6-host%zone]:port" and resolves a pair of domain name and
// numeric addresses on the network net, which must be "tcp", // port name on the network net, which must be "tcp", "tcp4" or
// "tcp4" or "tcp6". A literal IPv6 host address must be // "tcp6". A literal address or host name for IPv6 must be enclosed
// enclosed in square brackets, as in "[::]:80". // in square brackets, as in "[::1]:80", "[ipv6-host]:http" or
// "[ipv6-host%zone]:80".
func ResolveTCPAddr(net, addr string) (*TCPAddr, error) { func ResolveTCPAddr(net, addr string) (*TCPAddr, error) {
switch net { switch net {
case "tcp", "tcp4", "tcp6": case "tcp", "tcp4", "tcp6":

View file

@ -5,6 +5,7 @@
package net package net
import ( import (
"fmt"
"reflect" "reflect"
"runtime" "runtime"
"testing" "testing"
@ -22,6 +23,11 @@ var resolveUDPAddrTests = []struct {
{"udp", "[::1]:1", &UDPAddr{IP: ParseIP("::1"), Port: 1}, nil}, {"udp", "[::1]:1", &UDPAddr{IP: ParseIP("::1"), Port: 1}, nil},
{"udp6", "[::1]:65534", &UDPAddr{IP: ParseIP("::1"), Port: 65534}, nil}, {"udp6", "[::1]:65534", &UDPAddr{IP: ParseIP("::1"), Port: 65534}, nil},
{"udp", "[::1%en0]:1", &UDPAddr{IP: ParseIP("::1"), Port: 1, Zone: "en0"}, nil},
{"udp6", "[::1%911]:2", &UDPAddr{IP: ParseIP("::1"), Port: 2, Zone: "911"}, nil},
{"udp6", "[fe80::1]:3", &UDPAddr{IP: ParseIP("fe80::1"), Port: 3, Zone: "name"}, nil},
{"udp6", "[fe80::1]:4", &UDPAddr{IP: ParseIP("fe80::1"), Port: 4, Zone: "index"}, nil},
{"", "127.0.0.1:0", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 0}, nil}, // Go 1.0 behavior {"", "127.0.0.1:0", &UDPAddr{IP: IPv4(127, 0, 0, 1), Port: 0}, nil}, // Go 1.0 behavior
{"", "[::1]:0", &UDPAddr{IP: ParseIP("::1"), Port: 0}, nil}, // Go 1.0 behavior {"", "[::1]:0", &UDPAddr{IP: ParseIP("::1"), Port: 0}, nil}, // Go 1.0 behavior
@ -30,6 +36,24 @@ var resolveUDPAddrTests = []struct {
func TestResolveUDPAddr(t *testing.T) { func TestResolveUDPAddr(t *testing.T) {
for _, tt := range resolveUDPAddrTests { for _, tt := range resolveUDPAddrTests {
if tt.addr != nil && (tt.addr.Zone == "name" || tt.addr.Zone == "index") {
ifi := loopbackInterface()
if ifi == nil {
continue
}
i := last(tt.litAddr, ']')
if i > 0 {
switch tt.addr.Zone {
case "name":
tt.litAddr = tt.litAddr[:i] + "%" + ifi.Name + tt.litAddr[i:]
tt.addr.Zone = zoneToString(ifi.Index)
case "index":
index := fmt.Sprintf("%v", ifi.Index)
tt.litAddr = tt.litAddr[:i] + "%" + index + tt.litAddr[i:]
tt.addr.Zone = index
}
}
}
addr, err := ResolveUDPAddr(tt.net, tt.litAddr) addr, err := ResolveUDPAddr(tt.net, tt.litAddr)
if err != tt.err { if err != tt.err {
t.Fatalf("ResolveUDPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err) t.Fatalf("ResolveUDPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
@ -146,3 +170,78 @@ func TestUDPConnLocalName(t *testing.T) {
} }
} }
} }
func TestIPv6LinkLocalUnicastUDP(t *testing.T) {
if testing.Short() || !*testExternal {
t.Skip("skipping test to avoid external network")
}
if !supportsIPv6 {
t.Skip("ipv6 is not supported")
}
ifi := loopbackInterface()
if ifi == nil {
t.Skip("loopback interface not found")
}
laddr := ipv6LinkLocalUnicastAddr(ifi)
if laddr == "" {
t.Skip("ipv6 unicast address on loopback not found")
}
type test struct {
net, addr string
nameLookup bool
}
var tests = []test{
{"udp", "[" + laddr + "%" + ifi.Name + "]:0", false},
{"udp6", "[" + laddr + "%" + ifi.Name + "]:0", false},
}
switch runtime.GOOS {
case "darwin", "freebsd", "openbsd", "netbsd":
tests = append(tests, []test{
{"udp", "[localhost%" + ifi.Name + "]:0", true},
{"udp6", "[localhost%" + ifi.Name + "]:0", true},
}...)
case "linux":
tests = append(tests, []test{
{"udp", "[ip6-localhost%" + ifi.Name + "]:0", true},
{"udp6", "[ip6-localhost%" + ifi.Name + "]:0", true},
}...)
}
for _, tt := range tests {
c1, err := ListenPacket(tt.net, tt.addr)
if err != nil {
// It might return "LookupHost returned no
// suitable address" error on some platforms.
t.Logf("ListenPacket failed: %v", err)
continue
}
defer c1.Close()
if la, ok := c1.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" {
t.Fatalf("got %v; expected a proper address with zone identifier", la)
}
c2, err := Dial(tt.net, c1.LocalAddr().String())
if err != nil {
t.Fatalf("Dial failed: %v", err)
}
defer c2.Close()
if la, ok := c2.LocalAddr().(*UDPAddr); !ok || !tt.nameLookup && la.Zone == "" {
t.Fatalf("got %v; expected a proper address with zone identifier", la)
}
if ra, ok := c2.RemoteAddr().(*UDPAddr); !ok || !tt.nameLookup && ra.Zone == "" {
t.Fatalf("got %v; expected a proper address with zone identifier", ra)
}
if _, err := c2.Write([]byte("UDP OVER IPV6 LINKLOCAL TEST")); err != nil {
t.Fatalf("Conn.Write failed: %v", err)
}
b := make([]byte, 32)
if _, from, err := c1.ReadFrom(b); err != nil {
t.Fatalf("PacketConn.ReadFrom failed: %v", err)
} else {
if ra, ok := from.(*UDPAddr); !ok || !tt.nameLookup && ra.Zone == "" {
t.Fatalf("got %v; expected a proper address with zone identifier", ra)
}
}
}
}

View file

@ -24,14 +24,18 @@ func (a *UDPAddr) String() string {
if a == nil { if a == nil {
return "<nil>" return "<nil>"
} }
if a.Zone != "" {
return JoinHostPort(a.IP.String()+"%"+a.Zone, itoa(a.Port))
}
return JoinHostPort(a.IP.String(), itoa(a.Port)) return JoinHostPort(a.IP.String(), itoa(a.Port))
} }
// ResolveUDPAddr parses addr as a UDP address of the form // ResolveUDPAddr parses addr as a UDP address of the form "host:port"
// host:port and resolves domain names or port names to // or "[ipv6-host%zone]:port" and resolves a pair of domain name and
// numeric addresses on the network net, which must be "udp", // port name on the network net, which must be "udp", "udp4" or
// "udp4" or "udp6". A literal IPv6 host address must be // "udp6". A literal address or host name for IPv6 must be enclosed
// enclosed in square brackets, as in "[::]:80". // in square brackets, as in "[::1]:80", "[ipv6-host]:http" or
// "[ipv6-host%zone]:80".
func ResolveUDPAddr(net, addr string) (*UDPAddr, error) { func ResolveUDPAddr(net, addr string) (*UDPAddr, error) {
switch net { switch net {
case "udp", "udp4", "udp6": case "udp", "udp4", "udp6":