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
+}