nettest: move helpers from internal/nettest package

This change moves some test helper functions from the internal/nettest
package to avoid vendoring of internal/nettest package in the standard
library.

Change-Id: I2dfe6817bc660a76919460c3975b72be1a2b65f3
Reviewed-on: https://go-review.googlesource.com/c/net/+/123055
Run-TryBot: Mikio Hara <mikioh.public.networking@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/nettest/conntest.go b/nettest/conntest.go
index ff9b20b..39cc6a6 100644
--- a/nettest/conntest.go
+++ b/nettest/conntest.go
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package nettest provides utilities for network testing.
 package nettest
 
 import (
@@ -18,11 +17,6 @@
 	"time"
 )
 
-var (
-	aLongTimeAgo = time.Unix(233431200, 0)
-	neverTimeout = time.Time{}
-)
-
 // MakePipe creates a connection between two endpoints and returns the pair
 // as c1 and c2, such that anything written to c1 is read by c2 and vice-versa.
 // The stop function closes all resources, including c1, c2, and the underlying
@@ -96,7 +90,7 @@
 	}()
 
 	if got := <-dataCh; !bytes.Equal(got, want) {
-		t.Errorf("transmitted data differs")
+		t.Error("transmitted data differs")
 	}
 }
 
diff --git a/nettest/conntest_test.go b/nettest/conntest_test.go
index 9f9453f..da24168 100644
--- a/nettest/conntest_test.go
+++ b/nettest/conntest_test.go
@@ -11,8 +11,6 @@
 	"os"
 	"runtime"
 	"testing"
-
-	"golang.org/x/net/internal/nettest"
 )
 
 func TestTestConn(t *testing.T) {
@@ -24,12 +22,12 @@
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if !nettest.TestableNetwork(tt.network) {
-				t.Skipf("not supported on %s", runtime.GOOS)
+			if !TestableNetwork(tt.network) {
+				t.Skipf("%s not supported on %s/%s", tt.network, runtime.GOOS, runtime.GOARCH)
 			}
 
 			mp := func() (c1, c2 net.Conn, stop func(), err error) {
-				ln, err := nettest.NewLocalListener(tt.network)
+				ln, err := NewLocalListener(tt.network)
 				if err != nil {
 					return nil, nil, nil, err
 				}
diff --git a/nettest/nettest.go b/nettest/nettest.go
new file mode 100644
index 0000000..c75facf
--- /dev/null
+++ b/nettest/nettest.go
@@ -0,0 +1,338 @@
+// Copyright 2019 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 nettest provides utilities for network testing.
+package nettest
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"os"
+	"os/exec"
+	"runtime"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+var (
+	stackOnce   sync.Once
+	ipv4Enabled bool
+	ipv6Enabled bool
+	aixTechLvl  int
+
+	aLongTimeAgo = time.Unix(233431200, 0)
+	neverTimeout = time.Time{}
+
+	errNoAvailableInterface = errors.New("no available interface")
+	errNoAvailableAddress   = errors.New("no available address")
+)
+
+func probeStack() {
+	if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
+		ln.Close()
+		ipv4Enabled = true
+	}
+	if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
+		ln.Close()
+		ipv6Enabled = true
+	}
+	if runtime.GOOS == "aix" {
+		out, err := exec.Command("oslevel", "-s").Output()
+		if err != nil {
+			return
+		}
+		aixTechLvl, _ = strconv.Atoi(string(out[5:7]))
+	}
+}
+
+func aixTechLevel() int {
+	stackOnce.Do(probeStack)
+	return aixTechLvl
+}
+
+// SupportsIPv4 reports whether the platform supports IPv4 networking
+// functionality.
+func SupportsIPv4() bool {
+	stackOnce.Do(probeStack)
+	return ipv4Enabled
+}
+
+// SupportsIPv6 reports whether the platform supports IPv6 networking
+// functionality.
+func SupportsIPv6() bool {
+	stackOnce.Do(probeStack)
+	return ipv6Enabled
+}
+
+// TestableNetwork reports whether network is testable on the current
+// platform configuration.
+func TestableNetwork(network string) bool {
+	ss := strings.Split(network, ":")
+	switch ss[0] {
+	case "ip+nopriv":
+		// This is an internal network name for testing on the
+		// package net of the standard library.
+		switch runtime.GOOS {
+		case "android", "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
+			return false
+		case "darwin":
+			// iOS doesn't support it.
+			if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
+				return false
+			}
+		}
+	case "ip", "ip4", "ip6":
+		switch runtime.GOOS {
+		case "fuchsia", "hurd", "js", "nacl", "plan9":
+			return false
+		default:
+			if os.Getuid() != 0 {
+				return false
+			}
+		}
+	case "unix", "unixgram":
+		switch runtime.GOOS {
+		case "android", "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
+			return false
+		case "aix":
+			// Unix network isn't properly working on AIX
+			// 7.2 with Technical Level < 2.
+			if aixTechLevel() < 2 {
+				return false
+			}
+			return true
+		case "darwin":
+			// iOS does not support unix, unixgram.
+			if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
+				return false
+			}
+		}
+	case "unixpacket":
+		switch runtime.GOOS {
+		case "aix", "android", "fuchsia", "hurd", "darwin", "js", "nacl", "plan9", "windows":
+			return false
+		case "netbsd":
+			// It passes on amd64 at least. 386 fails
+			// (Issue 22927). arm is unknown.
+			if runtime.GOARCH == "386" {
+				return false
+			}
+		}
+	}
+	switch ss[0] {
+	case "tcp4", "udp4", "ip4":
+		return SupportsIPv4()
+	case "tcp6", "udp6", "ip6":
+		return SupportsIPv6()
+	}
+	return true
+}
+
+// TestableAddress reports whether address of network is testable on
+// the current platform configuration.
+func TestableAddress(network, address string) bool {
+	switch ss := strings.Split(network, ":"); ss[0] {
+	case "unix", "unixgram", "unixpacket":
+		// Abstract unix domain sockets, a Linux-ism.
+		if address[0] == '@' && runtime.GOOS != "linux" {
+			return false
+		}
+	}
+	return true
+}
+
+// NewLocalListener returns a listener which listens to a loopback IP
+// address or local file system path.
+//
+// The provided 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":
+		path, err := LocalPath()
+		if err != nil {
+			return nil, err
+		}
+		return net.Listen(network, path)
+	}
+	return nil, fmt.Errorf("%s is not supported on %s/%s", network, runtime.GOOS, runtime.GOARCH)
+}
+
+// NewLocalPacketListener returns a packet listener which listens to a
+// loopback IP address or local file system path.
+//
+// The provided 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":
+		path, err := LocalPath()
+		if err != nil {
+			return nil, err
+		}
+		return net.ListenPacket(network, path)
+	}
+	return nil, fmt.Errorf("%s is not supported on %s/%s", network, runtime.GOOS, runtime.GOARCH)
+}
+
+// LocalPath returns a local path that can be used for Unix-domain
+// protocol testing.
+func LocalPath() (string, error) {
+	f, err := ioutil.TempFile("", "go-nettest")
+	if err != nil {
+		return "", err
+	}
+	path := f.Name()
+	f.Close()
+	os.Remove(path)
+	return path, nil
+}
+
+// MulticastSource returns a unicast IP address on ifi when ifi is an
+// IP multicast-capable network interface.
+//
+// The provided network must be "ip", "ip4" or "ip6".
+func MulticastSource(network string, ifi *net.Interface) (net.IP, error) {
+	switch network {
+	case "ip", "ip4", "ip6":
+	default:
+		return nil, errNoAvailableAddress
+	}
+	if ifi == nil || ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagMulticast == 0 {
+		return nil, errNoAvailableAddress
+	}
+	ip, ok := hasRoutableIP(network, ifi)
+	if !ok {
+		return nil, errNoAvailableAddress
+	}
+	return ip, nil
+}
+
+// LoopbackInterface returns an available logical network interface
+// for loopback test.
+// It returns nil if no suitable interface is found.
+func LoopbackInterface() (*net.Interface, error) {
+	ift, err := net.Interfaces()
+	if err != nil {
+		return nil, errNoAvailableInterface
+	}
+	for _, ifi := range ift {
+		if ifi.Flags&net.FlagLoopback != 0 && ifi.Flags&net.FlagUp != 0 {
+			return &ifi, nil
+		}
+	}
+	return nil, errNoAvailableInterface
+}
+
+// RoutedInterface returns a network interface that can route IP
+// traffic and satisfies flags.
+// It returns nil when an appropriate network interface is not
+// found.
+//
+// The provided network must be "ip", "ip4" or "ip6".
+func RoutedInterface(network string, flags net.Flags) (*net.Interface, error) {
+	switch network {
+	case "ip", "ip4", "ip6":
+	default:
+		return nil, errNoAvailableInterface
+	}
+	ift, err := net.Interfaces()
+	if err != nil {
+		return nil, errNoAvailableInterface
+	}
+	for _, ifi := range ift {
+		if ifi.Flags&flags != flags {
+			continue
+		}
+		if _, ok := hasRoutableIP(network, &ifi); !ok {
+			continue
+		}
+		return &ifi, nil
+	}
+	return nil, errNoAvailableInterface
+}
+
+func hasRoutableIP(network string, ifi *net.Interface) (net.IP, bool) {
+	ifat, err := ifi.Addrs()
+	if err != nil {
+		return nil, false
+	}
+	for _, ifa := range ifat {
+		switch ifa := ifa.(type) {
+		case *net.IPAddr:
+			if ip, ok := routableIP(network, ifa.IP); ok {
+				return ip, true
+			}
+		case *net.IPNet:
+			if ip, ok := routableIP(network, ifa.IP); ok {
+				return ip, true
+			}
+		}
+	}
+	return nil, false
+}
+
+func routableIP(network string, ip net.IP) (net.IP, bool) {
+	if !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && !ip.IsGlobalUnicast() {
+		return nil, false
+	}
+	switch network {
+	case "ip4":
+		if ip := ip.To4(); ip != nil {
+			return ip, true
+		}
+	case "ip6":
+		if ip.IsLoopback() { // addressing scope of the loopback address depends on each implementation
+			return nil, false
+		}
+		if ip := ip.To16(); ip != nil && ip.To4() == nil {
+			return ip, true
+		}
+	default:
+		if ip := ip.To4(); ip != nil {
+			return ip, true
+		}
+		if ip := ip.To16(); ip != nil {
+			return ip, true
+		}
+	}
+	return nil, false
+}