golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/nettest/nettest.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package nettest provides utilities for network testing.
     6  package nettest
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net"
    13  	"os"
    14  	"os/exec"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  )
    21  
    22  var (
    23  	stackOnce               sync.Once
    24  	ipv4Enabled             bool
    25  	canListenTCP4OnLoopback bool
    26  	ipv6Enabled             bool
    27  	canListenTCP6OnLoopback bool
    28  	unStrmDgramEnabled      bool
    29  	rawSocketSess           bool
    30  
    31  	aLongTimeAgo = time.Unix(233431200, 0)
    32  	neverTimeout = time.Time{}
    33  
    34  	errNoAvailableInterface = errors.New("no available interface")
    35  	errNoAvailableAddress   = errors.New("no available address")
    36  )
    37  
    38  func probeStack() {
    39  	if _, err := RoutedInterface("ip4", net.FlagUp); err == nil {
    40  		ipv4Enabled = true
    41  	}
    42  	if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
    43  		ln.Close()
    44  		canListenTCP4OnLoopback = true
    45  	}
    46  	if _, err := RoutedInterface("ip6", net.FlagUp); err == nil {
    47  		ipv6Enabled = true
    48  	}
    49  	if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
    50  		ln.Close()
    51  		canListenTCP6OnLoopback = true
    52  	}
    53  	rawSocketSess = supportsRawSocket()
    54  	switch runtime.GOOS {
    55  	case "aix":
    56  		// Unix network isn't properly working on AIX 7.2 with
    57  		// Technical Level < 2.
    58  		out, _ := exec.Command("oslevel", "-s").Output()
    59  		if len(out) >= len("7200-XX-ZZ-YYMM") { // AIX 7.2, Tech Level XX, Service Pack ZZ, date YYMM
    60  			ver := string(out[:4])
    61  			tl, _ := strconv.Atoi(string(out[5:7]))
    62  			unStrmDgramEnabled = ver > "7200" || (ver == "7200" && tl >= 2)
    63  		}
    64  	default:
    65  		unStrmDgramEnabled = true
    66  	}
    67  }
    68  
    69  func unixStrmDgramEnabled() bool {
    70  	stackOnce.Do(probeStack)
    71  	return unStrmDgramEnabled
    72  }
    73  
    74  // SupportsIPv4 reports whether the platform supports IPv4 networking
    75  // functionality.
    76  func SupportsIPv4() bool {
    77  	stackOnce.Do(probeStack)
    78  	return ipv4Enabled
    79  }
    80  
    81  // SupportsIPv6 reports whether the platform supports IPv6 networking
    82  // functionality.
    83  func SupportsIPv6() bool {
    84  	stackOnce.Do(probeStack)
    85  	return ipv6Enabled
    86  }
    87  
    88  // SupportsRawSocket reports whether the current session is available
    89  // to use raw sockets.
    90  func SupportsRawSocket() bool {
    91  	stackOnce.Do(probeStack)
    92  	return rawSocketSess
    93  }
    94  
    95  // TestableNetwork reports whether network is testable on the current
    96  // platform configuration.
    97  //
    98  // See func Dial of the standard library for the supported networks.
    99  func TestableNetwork(network string) bool {
   100  	ss := strings.Split(network, ":")
   101  	switch ss[0] {
   102  	case "ip+nopriv":
   103  		// This is an internal network name for testing on the
   104  		// package net of the standard library.
   105  		switch runtime.GOOS {
   106  		case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "wasip1", "windows":
   107  			return false
   108  		}
   109  	case "ip", "ip4", "ip6":
   110  		switch runtime.GOOS {
   111  		case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1":
   112  			return false
   113  		default:
   114  			if os.Getuid() != 0 {
   115  				return false
   116  			}
   117  		}
   118  	case "unix", "unixgram":
   119  		switch runtime.GOOS {
   120  		case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "wasip1", "windows":
   121  			return false
   122  		case "aix":
   123  			return unixStrmDgramEnabled()
   124  		}
   125  	case "unixpacket":
   126  		switch runtime.GOOS {
   127  		case "aix", "android", "fuchsia", "hurd", "darwin", "ios", "js", "nacl", "plan9", "wasip1", "windows", "zos":
   128  			return false
   129  		}
   130  	}
   131  	switch ss[0] {
   132  	case "tcp4", "udp4", "ip4":
   133  		return SupportsIPv4()
   134  	case "tcp6", "udp6", "ip6":
   135  		return SupportsIPv6()
   136  	}
   137  	return true
   138  }
   139  
   140  // TestableAddress reports whether address of network is testable on
   141  // the current platform configuration.
   142  func TestableAddress(network, address string) bool {
   143  	switch ss := strings.Split(network, ":"); ss[0] {
   144  	case "unix", "unixgram", "unixpacket":
   145  		// Abstract unix domain sockets, a Linux-ism.
   146  		if address[0] == '@' && runtime.GOOS != "linux" {
   147  			return false
   148  		}
   149  	}
   150  	return true
   151  }
   152  
   153  // NewLocalListener returns a listener which listens to a loopback IP
   154  // address or local file system path.
   155  //
   156  // The provided network must be "tcp", "tcp4", "tcp6", "unix" or
   157  // "unixpacket".
   158  func NewLocalListener(network string) (net.Listener, error) {
   159  	stackOnce.Do(probeStack)
   160  	switch network {
   161  	case "tcp":
   162  		if canListenTCP4OnLoopback {
   163  			if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
   164  				return ln, nil
   165  			}
   166  		}
   167  		if canListenTCP6OnLoopback {
   168  			return net.Listen("tcp6", "[::1]:0")
   169  		}
   170  	case "tcp4":
   171  		if canListenTCP4OnLoopback {
   172  			return net.Listen("tcp4", "127.0.0.1:0")
   173  		}
   174  	case "tcp6":
   175  		if canListenTCP6OnLoopback {
   176  			return net.Listen("tcp6", "[::1]:0")
   177  		}
   178  	case "unix", "unixpacket":
   179  		path, err := LocalPath()
   180  		if err != nil {
   181  			return nil, err
   182  		}
   183  		return net.Listen(network, path)
   184  	}
   185  	return nil, fmt.Errorf("%s is not supported on %s/%s", network, runtime.GOOS, runtime.GOARCH)
   186  }
   187  
   188  // NewLocalPacketListener returns a packet listener which listens to a
   189  // loopback IP address or local file system path.
   190  //
   191  // The provided network must be "udp", "udp4", "udp6" or "unixgram".
   192  func NewLocalPacketListener(network string) (net.PacketConn, error) {
   193  	stackOnce.Do(probeStack)
   194  	switch network {
   195  	case "udp":
   196  		if canListenTCP4OnLoopback {
   197  			if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil {
   198  				return c, nil
   199  			}
   200  		}
   201  		if canListenTCP6OnLoopback {
   202  			return net.ListenPacket("udp6", "[::1]:0")
   203  		}
   204  	case "udp4":
   205  		if canListenTCP4OnLoopback {
   206  			return net.ListenPacket("udp4", "127.0.0.1:0")
   207  		}
   208  	case "udp6":
   209  		if canListenTCP6OnLoopback {
   210  			return net.ListenPacket("udp6", "[::1]:0")
   211  		}
   212  	case "unixgram":
   213  		path, err := LocalPath()
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		return net.ListenPacket(network, path)
   218  	}
   219  	return nil, fmt.Errorf("%s is not supported on %s/%s", network, runtime.GOOS, runtime.GOARCH)
   220  }
   221  
   222  // LocalPath returns a local path that can be used for Unix-domain
   223  // protocol testing.
   224  func LocalPath() (string, error) {
   225  	dir := ""
   226  	if runtime.GOOS == "darwin" {
   227  		dir = "/tmp"
   228  	}
   229  	f, err := ioutil.TempFile(dir, "go-nettest")
   230  	if err != nil {
   231  		return "", err
   232  	}
   233  	path := f.Name()
   234  	f.Close()
   235  	os.Remove(path)
   236  	return path, nil
   237  }
   238  
   239  // MulticastSource returns a unicast IP address on ifi when ifi is an
   240  // IP multicast-capable network interface.
   241  //
   242  // The provided network must be "ip", "ip4" or "ip6".
   243  func MulticastSource(network string, ifi *net.Interface) (net.IP, error) {
   244  	switch network {
   245  	case "ip", "ip4", "ip6":
   246  	default:
   247  		return nil, errNoAvailableAddress
   248  	}
   249  	if ifi == nil || ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagMulticast == 0 {
   250  		return nil, errNoAvailableAddress
   251  	}
   252  	ip, ok := hasRoutableIP(network, ifi)
   253  	if !ok {
   254  		return nil, errNoAvailableAddress
   255  	}
   256  	return ip, nil
   257  }
   258  
   259  // LoopbackInterface returns an available logical network interface
   260  // for loopback test.
   261  func LoopbackInterface() (*net.Interface, error) {
   262  	ift, err := net.Interfaces()
   263  	if err != nil {
   264  		return nil, errNoAvailableInterface
   265  	}
   266  	for _, ifi := range ift {
   267  		if ifi.Flags&net.FlagLoopback != 0 && ifi.Flags&net.FlagUp != 0 {
   268  			return &ifi, nil
   269  		}
   270  	}
   271  	return nil, errNoAvailableInterface
   272  }
   273  
   274  // RoutedInterface returns a network interface that can route IP
   275  // traffic and satisfies flags.
   276  //
   277  // The provided network must be "ip", "ip4" or "ip6".
   278  func RoutedInterface(network string, flags net.Flags) (*net.Interface, error) {
   279  	switch network {
   280  	case "ip", "ip4", "ip6":
   281  	default:
   282  		return nil, errNoAvailableInterface
   283  	}
   284  	ift, err := net.Interfaces()
   285  	if err != nil {
   286  		return nil, errNoAvailableInterface
   287  	}
   288  	for _, ifi := range ift {
   289  		if ifi.Flags&flags != flags {
   290  			continue
   291  		}
   292  		if _, ok := hasRoutableIP(network, &ifi); !ok {
   293  			continue
   294  		}
   295  		return &ifi, nil
   296  	}
   297  	return nil, errNoAvailableInterface
   298  }
   299  
   300  func hasRoutableIP(network string, ifi *net.Interface) (net.IP, bool) {
   301  	ifat, err := ifi.Addrs()
   302  	if err != nil {
   303  		return nil, false
   304  	}
   305  	for _, ifa := range ifat {
   306  		switch ifa := ifa.(type) {
   307  		case *net.IPAddr:
   308  			if ip, ok := routableIP(network, ifa.IP); ok {
   309  				return ip, true
   310  			}
   311  		case *net.IPNet:
   312  			if ip, ok := routableIP(network, ifa.IP); ok {
   313  				return ip, true
   314  			}
   315  		}
   316  	}
   317  	return nil, false
   318  }
   319  
   320  func routableIP(network string, ip net.IP) (net.IP, bool) {
   321  	if !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && !ip.IsGlobalUnicast() {
   322  		return nil, false
   323  	}
   324  	switch network {
   325  	case "ip4":
   326  		if ip := ip.To4(); ip != nil {
   327  			return ip, true
   328  		}
   329  	case "ip6":
   330  		if ip.IsLoopback() { // addressing scope of the loopback address depends on each implementation
   331  			return nil, false
   332  		}
   333  		if ip := ip.To16(); ip != nil && ip.To4() == nil {
   334  			return ip, true
   335  		}
   336  	default:
   337  		if ip := ip.To4(); ip != nil {
   338  			return ip, true
   339  		}
   340  		if ip := ip.To16(); ip != nil {
   341  			return ip, true
   342  		}
   343  	}
   344  	return nil, false
   345  }