internal/nettest: add TestableNetwork, NewLocalListener and NewLocalPacketListener

Change-Id: I61475922625ce8dbeee4180807c3f021a502edf7
Reviewed-on: https://go-review.googlesource.com/34731
Run-TryBot: Mikio Hara <mikioh.mikioh@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/internal/nettest/stack.go b/internal/nettest/stack.go
index 5ab9530..2808b57 100644
--- a/internal/nettest/stack.go
+++ b/internal/nettest/stack.go
@@ -5,32 +5,37 @@
 // Package nettest provides utilities for IP testing.
 package nettest // import "golang.org/x/net/internal/nettest"
 
-import "net"
+import (
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"runtime"
+)
+
+var (
+	supportsIPv4 bool
+	supportsIPv6 bool
+)
+
+func init() {
+	if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
+		ln.Close()
+		supportsIPv4 = true
+	}
+	if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
+		ln.Close()
+		supportsIPv6 = true
+	}
+}
 
 // SupportsIPv4 reports whether the platform supports IPv4 networking
 // functionality.
-func SupportsIPv4() bool {
-	ln, err := net.Listen("tcp4", "127.0.0.1:0")
-	if err != nil {
-		return false
-	}
-	ln.Close()
-	return true
-}
+func SupportsIPv4() bool { return supportsIPv4 }
 
 // SupportsIPv6 reports whether the platform supports IPv6 networking
 // functionality.
-func SupportsIPv6() bool {
-	if causesIPv6Crash() {
-		return false
-	}
-	ln, err := net.Listen("tcp6", "[::1]:0")
-	if err != nil {
-		return false
-	}
-	ln.Close()
-	return true
-}
+func SupportsIPv6() bool { return supportsIPv6 }
 
 // SupportsRawIPSocket reports whether the platform supports raw IP
 // sockets.
@@ -50,3 +55,93 @@
 func ProtocolNotSupported(err error) bool {
 	return protocolNotSupported(err)
 }
+
+// TestableNetwork reports whether network is testable on the current
+// platform configuration.
+func TestableNetwork(network string) bool {
+	// This is based on logic from standard library's
+	// net/platform_test.go.
+	switch network {
+	case "unix":
+		switch runtime.GOOS {
+		case "android", "nacl", "plan9", "windows":
+			return false
+		}
+		if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
+			return false
+		}
+	case "unixpacket":
+		switch runtime.GOOS {
+		case "android", "darwin", "freebsd", "nacl", "plan9", "windows":
+			return false
+		}
+	}
+	return true
+}
+
+// NewLocalListener returns a listener which listens to a loopback IP
+// address or local file system path.
+// Network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket".
+func NewLocalListener(network string) (net.Listener, error) {
+	switch network {
+	case "tcp":
+		if supportsIPv4 {
+			if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
+				return ln, nil
+			}
+		}
+		if supportsIPv6 {
+			return net.Listen("tcp6", "[::1]:0")
+		}
+	case "tcp4":
+		if supportsIPv4 {
+			return net.Listen("tcp4", "127.0.0.1:0")
+		}
+	case "tcp6":
+		if supportsIPv6 {
+			return net.Listen("tcp6", "[::1]:0")
+		}
+	case "unix", "unixpacket":
+		return net.Listen(network, localPath())
+	}
+	return nil, fmt.Errorf("%s is not supported", network)
+}
+
+// NewLocalPacketListener returns a packet listener which listens to a
+// loopback IP address or local file system path.
+// Network must be "udp", "udp4", "udp6" or "unixgram".
+func NewLocalPacketListener(network string) (net.PacketConn, error) {
+	switch network {
+	case "udp":
+		if supportsIPv4 {
+			if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil {
+				return c, nil
+			}
+		}
+		if supportsIPv6 {
+			return net.ListenPacket("udp6", "[::1]:0")
+		}
+	case "udp4":
+		if supportsIPv4 {
+			return net.ListenPacket("udp4", "127.0.0.1:0")
+		}
+	case "udp6":
+		if supportsIPv6 {
+			return net.ListenPacket("udp6", "[::1]:0")
+		}
+	case "unixgram":
+		return net.ListenPacket(network, localPath())
+	}
+	return nil, fmt.Errorf("%s is not supported", network)
+}
+
+func localPath() string {
+	f, err := ioutil.TempFile("", "nettest")
+	if err != nil {
+		panic(err)
+	}
+	path := f.Name()
+	f.Close()
+	os.Remove(path)
+	return path
+}