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