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
diff --git a/src/pkg/net/dial.go b/src/pkg/net/dial.go
index c0e4a22..da5f7e3 100644
--- a/src/pkg/net/dial.go
+++ b/src/pkg/net/dial.go
@@ -169,22 +169,27 @@
 // "unixpacket".
 //
 // For TCP and UDP networks, addresses have the form host:port.
-// If host is a literal IPv6 address, it must be enclosed
-// in square brackets.  The functions JoinHostPort and SplitHostPort
-// manipulate addresses in this form.
+// If host is a literal IPv6 address or host name, it must be enclosed
+// in square brackets as in "[::1]:80", "[ipv6-host]:http" or
+// "[ipv6-host%zone]:80".
+// The functions JoinHostPort and SplitHostPort manipulate addresses
+// in this form.
 //
 // Examples:
 //	Dial("tcp", "12.34.56.78:80")
-//	Dial("tcp", "google.com:80")
-//	Dial("tcp", "[de:ad:be:ef::ca:fe]:80")
+//	Dial("tcp", "google.com:http")
+//	Dial("tcp", "[2001:db8::1]:http")
+//	Dial("tcp", "[fe80::1%lo0]:80")
 //
-// For IP networks, net must be "ip", "ip4" or "ip6" followed
-// by a colon and a protocol number or name.
+// For IP networks, the net must be "ip", "ip4" or "ip6" followed by a
+// colon and a protocol number or name and the addr must be a literal
+// IP address.
 //
 // Examples:
 //	Dial("ip4:1", "127.0.0.1")
 //	Dial("ip6:ospf", "::1")
 //
+// For Unix networks, the addr must be a file system path.
 func Dial(net, addr string) (Conn, error) {
 	return DialOpt(addr, dialNetwork(net))
 }
@@ -290,8 +295,9 @@
 func (a stringAddr) String() string  { return a.addr }
 
 // Listen announces on the local network address laddr.
-// The network string net must be a stream-oriented network:
-// "tcp", "tcp4", "tcp6", "unix" or "unixpacket".
+// The network net must be a stream-oriented network: "tcp", "tcp4",
+// "tcp6", "unix" or "unixpacket".
+// See Dial for the syntax of laddr.
 func Listen(net, laddr string) (Listener, error) {
 	la, err := resolveAddr("listen", net, laddr, noDeadline)
 	if err != nil {
@@ -307,8 +313,9 @@
 }
 
 // ListenPacket announces on the local network address laddr.
-// The network string net must be a packet-oriented network:
-// "udp", "udp4", "udp6", "ip", "ip4", "ip6" or "unixgram".
+// The network net must be a packet-oriented network: "udp", "udp4",
+// "udp6", "ip", "ip4", "ip6" or "unixgram".
+// See Dial for the syntax of laddr.
 func ListenPacket(net, laddr string) (PacketConn, error) {
 	la, err := resolveAddr("listen", net, laddr, noDeadline)
 	if err != nil {
diff --git a/src/pkg/net/interface_test.go b/src/pkg/net/interface_test.go
index 7fb3428..e31894a 100644
--- a/src/pkg/net/interface_test.go
+++ b/src/pkg/net/interface_test.go
@@ -25,6 +25,32 @@
 	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) {
 	ift, err := Interfaces()
 	if err != nil {
@@ -81,9 +107,9 @@
 
 func testAddrs(t *testing.T, ifat []Addr) {
 	for _, ifa := range ifat {
-		switch v := ifa.(type) {
+		switch ifa := ifa.(type) {
 		case *IPAddr, *IPNet:
-			if v == nil {
+			if ifa == nil {
 				t.Errorf("\tunexpected value: %v", ifa)
 			} else {
 				t.Logf("\tinterface address %q", ifa.String())
@@ -96,9 +122,9 @@
 
 func testMulticastAddrs(t *testing.T, ifmat []Addr) {
 	for _, ifma := range ifmat {
-		switch v := ifma.(type) {
+		switch ifma := ifma.(type) {
 		case *IPAddr:
-			if v == nil {
+			if ifma == nil {
 				t.Errorf("\tunexpected value: %v", ifma)
 			} else {
 				t.Logf("\tjoined group address %q", ifma.String())
diff --git a/src/pkg/net/ip.go b/src/pkg/net/ip.go
index b92b948..0e42da2 100644
--- a/src/pkg/net/ip.go
+++ b/src/pkg/net/ip.go
@@ -431,6 +431,9 @@
 	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"
 // or "2001:DB8::/48" as defined in RFC 4632 and RFC 4291.
 // If the mask is not in the canonical form, it returns the
@@ -449,9 +452,6 @@
 	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).
 func parseIPv4(s string) IP {
 	var p [IPv4len]byte
@@ -483,26 +483,26 @@
 	return IPv4(p[0], p[1], p[2], p[3])
 }
 
-// Parse IPv6 address.  Many forms.
-// The basic form is a sequence of eight colon-separated
-// 16-bit hex numbers separated by colons,
-// as in 0123:4567:89ab:cdef:0123:4567:89ab:cdef.
-// Two exceptions:
-//	* A run of zeros can be replaced with "::".
-//	* 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)
+// parseIPv6 parses s as a literal IPv6 address described in RFC 4291
+// and RFC 5952.  It can also parse a literal scoped IPv6 address with
+// zone identifier which is described in RFC 4007 when zoneAllowed is
+// true.
+func parseIPv6(s string, zoneAllowed bool) (ip IP, zone string) {
+	ip = make(IP, IPv6len)
 	ellipsis := -1 // position of ellipsis in p
 	i := 0         // index in string s
 
+	if zoneAllowed {
+		s, zone = splitHostZone(s)
+	}
+
 	// Might have leading ellipsis
 	if len(s) >= 2 && s[0] == ':' && s[1] == ':' {
 		ellipsis = 0
 		i = 2
 		// Might be only ellipsis
 		if i == len(s) {
-			return p
+			return ip, zone
 		}
 	}
 
@@ -512,35 +512,35 @@
 		// Hex number.
 		n, i1, ok := xtoi(s, i)
 		if !ok || n > 0xFFFF {
-			return nil
+			return nil, zone
 		}
 
 		// If followed by dot, might be in trailing IPv4.
 		if i1 < len(s) && s[i1] == '.' {
 			if ellipsis < 0 && j != IPv6len-IPv4len {
 				// Not the right place.
-				return nil
+				return nil, zone
 			}
 			if j+IPv4len > IPv6len {
 				// Not enough room.
-				return nil
+				return nil, zone
 			}
-			p4 := parseIPv4(s[i:])
-			if p4 == nil {
-				return nil
+			ip4 := parseIPv4(s[i:])
+			if ip4 == nil {
+				return nil, zone
 			}
-			p[j] = p4[12]
-			p[j+1] = p4[13]
-			p[j+2] = p4[14]
-			p[j+3] = p4[15]
+			ip[j] = ip4[12]
+			ip[j+1] = ip4[13]
+			ip[j+2] = ip4[14]
+			ip[j+3] = ip4[15]
 			i = len(s)
 			j += IPv4len
 			break
 		}
 
 		// Save this 16-bit chunk.
-		p[j] = byte(n >> 8)
-		p[j+1] = byte(n)
+		ip[j] = byte(n >> 8)
+		ip[j+1] = byte(n)
 		j += 2
 
 		// Stop at end of string.
@@ -551,14 +551,14 @@
 
 		// Otherwise must be followed by colon and more.
 		if s[i] != ':' || i+1 == len(s) {
-			return nil
+			return nil, zone
 		}
 		i++
 
 		// Look for ellipsis.
 		if s[i] == ':' {
 			if ellipsis >= 0 { // already have one
-				return nil
+				return nil, zone
 			}
 			ellipsis = j
 			if i++; i == len(s) { // can be at end
@@ -569,23 +569,23 @@
 
 	// Must have used entire string.
 	if i != len(s) {
-		return nil
+		return nil, zone
 	}
 
 	// If didn't parse enough, expand ellipsis.
 	if j < IPv6len {
 		if ellipsis < 0 {
-			return nil
+			return nil, zone
 		}
 		n := IPv6len - j
 		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-- {
-			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.
@@ -598,26 +598,17 @@
 	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.
 // The string s can be in dotted decimal ("74.125.19.99")
 // or IPv6 ("2001:4860:0:2001::68") form.
 // If s is not a valid textual representation of an IP address,
 // ParseIP returns nil.
 func ParseIP(s string) IP {
-	if p := parseIPv4(s); p != nil {
-		return p
+	if ip := parseIPv4(s); ip != nil {
+		return ip
 	}
-	return parseIPv6(s)
+	ip, _ := parseIPv6(s, false)
+	return ip
 }
 
 // ParseCIDR parses s as a CIDR notation IP address and mask,
@@ -632,15 +623,15 @@
 	if i < 0 {
 		return nil, nil, &ParseError{"CIDR address", s}
 	}
-	ipstr, maskstr := s[:i], s[i+1:]
+	addr, mask := s[:i], s[i+1:]
 	iplen := IPv4len
-	ip := parseIPv4(ipstr)
+	ip := parseIPv4(addr)
 	if ip == nil {
 		iplen = IPv6len
-		ip = parseIPv6(ipstr)
+		ip, _ = parseIPv6(addr, false)
 	}
-	n, i, ok := dtoi(maskstr, 0)
-	if ip == nil || !ok || i != len(maskstr) || n < 0 || n > 8*iplen {
+	n, i, ok := dtoi(mask, 0)
+	if ip == nil || !ok || i != len(mask) || n < 0 || n > 8*iplen {
 		return nil, nil, &ParseError{"CIDR address", s}
 	}
 	m := CIDRMask(n, 8*iplen)
diff --git a/src/pkg/net/ip_test.go b/src/pkg/net/ip_test.go
index 886f119..16f30d4 100644
--- a/src/pkg/net/ip_test.go
+++ b/src/pkg/net/ip_test.go
@@ -22,6 +22,8 @@
 	{"::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}},
 	{"::ffff:4a7d:1363", IPv4(74, 125, 19, 99)},
+	{"fe80::1%lo0", nil},
+	{"fe80::1%911", nil},
 	{"", nil},
 }
 
@@ -37,7 +39,6 @@
 	in  IP
 	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, 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"},
@@ -257,10 +258,13 @@
 	{"www.google.com", "80", "www.google.com:80"},
 	{"127.0.0.1", "1234", "127.0.0.1:1234"},
 	{"::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"},
-	{"127.0.0.1", "", "127.0.0.1:"},           // Go 1.0 behaviour
-	{"www.google.com", "", "www.google.com:"}, // Go 1.0 behaviour
+
+	{"google.com", "https%foo", "google.com:https%foo"}, // Go 1.0 behavior
+	{"127.0.0.1", "", "127.0.0.1:"},                     // Go 1.0 behaviour
+	{"www.google.com", "", "www.google.com:"},           // Go 1.0 behaviour
 }
 
 var splitFailureTests = []struct {
@@ -270,15 +274,27 @@
 	{"www.google.com", "missing port in address"},
 	{"127.0.0.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"},
+	{"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
+
 	{"[foo:bar]", "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", "too many colons in address"},
+
 	{"[foo]:[bar]baz", "unexpected '[' in address"},
 	{"foo[bar]:baz", "unexpected '[' in address"},
+
 	{"foo]bar:baz", "unexpected ']' in address"},
 }
 
diff --git a/src/pkg/net/ipraw_test.go b/src/pkg/net/ipraw_test.go
index fa1a535..841b57a 100644
--- a/src/pkg/net/ipraw_test.go
+++ b/src/pkg/net/ipraw_test.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"os"
 	"reflect"
 	"testing"
@@ -27,6 +28,11 @@
 	{"ip6", "::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
 	{"", "::1", &IPAddr{IP: ParseIP("::1")}, nil},           // Go 1.0 behavior
 
@@ -37,6 +43,21 @@
 
 func TestResolveIPAddr(t *testing.T) {
 	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)
 		if err != tt.err {
 			t.Fatalf("ResolveIPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
diff --git a/src/pkg/net/iprawsock.go b/src/pkg/net/iprawsock.go
index daccba3..9d99e75 100644
--- a/src/pkg/net/iprawsock.go
+++ b/src/pkg/net/iprawsock.go
@@ -19,12 +19,15 @@
 	if a == nil {
 		return "<nil>"
 	}
+	if a.Zone != "" {
+		return a.IP.String() + "%" + a.Zone
+	}
 	return a.IP.String()
 }
 
-// ResolveIPAddr parses addr as an IP address and resolves domain
-// names to numeric addresses on the network net, which must be
-// "ip", "ip4" or "ip6".
+// ResolveIPAddr parses addr as an IP address of the form "host" or
+// "ipv6-host%zone" and resolves the domain name on the network net,
+// which must be "ip", "ip4" or "ip6".
 func ResolveIPAddr(net, addr string) (*IPAddr, error) {
 	if net == "" { // a hint wildcard for Go 1.0 undocumented behavior
 		net = "ip"
diff --git a/src/pkg/net/ipsock.go b/src/pkg/net/ipsock.go
index 1ef4892..d930595 100644
--- a/src/pkg/net/ipsock.go
+++ b/src/pkg/net/ipsock.go
@@ -68,15 +68,12 @@
 func (e InvalidAddrError) Timeout() bool   { return false }
 func (e InvalidAddrError) Temporary() bool { return false }
 
-// SplitHostPort splits a network address of the form
-// "host:port" or "[host]:port" into host and port.
-// The latter form must be used when host contains a colon.
+// SplitHostPort splits a network address of the form "host:port",
+// "[host]:port" or "[ipv6-host%zone]:port" into host or
+// 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) {
-	host, port, _, err = splitHostPort(hostport)
-	return
-}
-
-func splitHostPort(hostport string) (host, port, zone string, err error) {
 	j, k := 0, 0
 
 	// The port starts after the last colon.
@@ -110,10 +107,12 @@
 		j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions
 	} else {
 		host = hostport[:i]
-
 		if byteIndex(host, ':') >= 0 {
 			goto tooManyColons
 		}
+		if byteIndex(host, '%') >= 0 {
+			goto missingBrackets
+		}
 	}
 	if byteIndex(hostport[j:], '[') >= 0 {
 		err = &AddrError{"unexpected '[' in address", hostport}
@@ -134,13 +133,29 @@
 tooManyColons:
 	err = &AddrError{"too many colons in address", hostport}
 	return
+
+missingBrackets:
+	err = &AddrError{"missing brackets in address", hostport}
+	return
 }
 
-// JoinHostPort combines host and port into a network address
-// of the form "host:port" or, if host contains a colon, "[host]:port".
+func splitHostZone(s string) (host, zone string) {
+	// 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 {
-	// If host has colons, have to bracket it.
-	if byteIndex(host, ':') >= 0 {
+	// If host has colons or a percent sign, have to bracket it.
+	if byteIndex(host, ':') >= 0 || byteIndex(host, '%') >= 0 {
 		return "[" + host + "]:" + port
 	}
 	return host + ":" + port
@@ -155,7 +170,7 @@
 	switch net {
 	case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
 		if addr != "" {
-			if host, port, zone, err = splitHostPort(addr); err != nil {
+			if host, port, err = SplitHostPort(addr); err != nil {
 				return nil, err
 			}
 			if portnum, err = parsePort(net, port); err != nil {
@@ -184,21 +199,25 @@
 		return inetaddr(net, nil, portnum, zone), nil
 	}
 	// Try as an IP address.
-	if ip := ParseIP(host); ip != nil {
+	if ip := parseIPv4(host); ip != 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
 	if net != "" && net[len(net)-1] == '4' {
 		filter = ipv4only
 	}
-	if net != "" && net[len(net)-1] == '6' {
+	if net != "" && net[len(net)-1] == '6' || zone != "" {
 		filter = ipv6only
 	}
-	// Try as a DNS name.
-	addrs, err := lookupHostDeadline(host, deadline)
-	if err != nil {
-		return nil, err
-	}
 	ip := firstFavoriteAddr(filter, addrs)
 	if ip == nil {
 		// should not happen
diff --git a/src/pkg/net/tcp_test.go b/src/pkg/net/tcp_test.go
index 6c4485a..add8e48 100644
--- a/src/pkg/net/tcp_test.go
+++ b/src/pkg/net/tcp_test.go
@@ -5,6 +5,7 @@
 package net
 
 import (
+	"fmt"
 	"reflect"
 	"runtime"
 	"testing"
@@ -158,6 +159,11 @@
 	{"tcp", "[::1]:1", &TCPAddr{IP: ParseIP("::1"), Port: 1}, 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
 	{"", "[::1]:0", &TCPAddr{IP: ParseIP("::1"), Port: 0}, nil},         // Go 1.0 behavior
 
@@ -166,6 +172,24 @@
 
 func TestResolveTCPAddr(t *testing.T) {
 	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)
 		if err != tt.err {
 			t.Fatalf("ResolveTCPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
@@ -204,3 +228,79 @@
 		}
 	}
 }
+
+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
+	}
+}
diff --git a/src/pkg/net/tcpsock.go b/src/pkg/net/tcpsock.go
index d5158b2..27db115 100644
--- a/src/pkg/net/tcpsock.go
+++ b/src/pkg/net/tcpsock.go
@@ -20,14 +20,18 @@
 	if a == nil {
 		return "<nil>"
 	}
+	if a.Zone != "" {
+		return JoinHostPort(a.IP.String()+"%"+a.Zone, itoa(a.Port))
+	}
 	return JoinHostPort(a.IP.String(), itoa(a.Port))
 }
 
-// ResolveTCPAddr parses addr as a TCP address of the form
-// host:port and resolves domain names or port names to
-// numeric addresses on the network net, which must be "tcp",
-// "tcp4" or "tcp6".  A literal IPv6 host address must be
-// enclosed in square brackets, as in "[::]:80".
+// ResolveTCPAddr parses addr as a TCP address of the form "host:port"
+// or "[ipv6-host%zone]:port" and resolves a pair of domain name and
+// port name on the network net, which must be "tcp", "tcp4" or
+// "tcp6".  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 ResolveTCPAddr(net, addr string) (*TCPAddr, error) {
 	switch net {
 	case "tcp", "tcp4", "tcp6":
diff --git a/src/pkg/net/udp_test.go b/src/pkg/net/udp_test.go
index 220422e..b3cafb0 100644
--- a/src/pkg/net/udp_test.go
+++ b/src/pkg/net/udp_test.go
@@ -5,6 +5,7 @@
 package net
 
 import (
+	"fmt"
 	"reflect"
 	"runtime"
 	"testing"
@@ -22,6 +23,11 @@
 	{"udp", "[::1]:1", &UDPAddr{IP: ParseIP("::1"), Port: 1}, 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
 	{"", "[::1]:0", &UDPAddr{IP: ParseIP("::1"), Port: 0}, nil},         // Go 1.0 behavior
 
@@ -30,6 +36,24 @@
 
 func TestResolveUDPAddr(t *testing.T) {
 	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)
 		if err != tt.err {
 			t.Fatalf("ResolveUDPAddr(%v, %v) failed: %v", tt.net, tt.litAddr, err)
@@ -146,3 +170,78 @@
 		}
 	}
 }
+
+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)
+			}
+		}
+	}
+}
diff --git a/src/pkg/net/udpsock.go b/src/pkg/net/udpsock.go
index 6e5e902..2770506 100644
--- a/src/pkg/net/udpsock.go
+++ b/src/pkg/net/udpsock.go
@@ -24,14 +24,18 @@
 	if a == nil {
 		return "<nil>"
 	}
+	if a.Zone != "" {
+		return JoinHostPort(a.IP.String()+"%"+a.Zone, itoa(a.Port))
+	}
 	return JoinHostPort(a.IP.String(), itoa(a.Port))
 }
 
-// ResolveUDPAddr parses addr as a UDP address of the form
-// host:port and resolves domain names or port names to
-// numeric addresses on the network net, which must be "udp",
-// "udp4" or "udp6".  A literal IPv6 host address must be
-// enclosed in square brackets, as in "[::]:80".
+// ResolveUDPAddr parses addr as a UDP address of the form "host:port"
+// or "[ipv6-host%zone]:port" and resolves a pair of domain name and
+// port name on the network net, which must be "udp", "udp4" or
+// "udp6".  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 ResolveUDPAddr(net, addr string) (*UDPAddr, error) {
 	switch net {
 	case "udp", "udp4", "udp6":