123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- // 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
- unStrmDgramEnabled bool
- rawSocketSess bool
- 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
- }
- rawSocketSess = supportsRawSocket()
- switch runtime.GOOS {
- case "aix":
- // Unix network isn't properly working on AIX 7.2 with
- // Technical Level < 2.
- out, _ := exec.Command("oslevel", "-s").Output()
- if len(out) >= len("7200-XX-ZZ-YYMM") { // AIX 7.2, Tech Level XX, Service Pack ZZ, date YYMM
- ver := string(out[:4])
- tl, _ := strconv.Atoi(string(out[5:7]))
- unStrmDgramEnabled = ver > "7200" || (ver == "7200" && tl >= 2)
- }
- default:
- unStrmDgramEnabled = true
- }
- }
- func unixStrmDgramEnabled() bool {
- stackOnce.Do(probeStack)
- return unStrmDgramEnabled
- }
- // 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
- }
- // SupportsRawSocket reports whether the current session is available
- // to use raw sockets.
- func SupportsRawSocket() bool {
- stackOnce.Do(probeStack)
- return rawSocketSess
- }
- // TestableNetwork reports whether network is testable on the current
- // platform configuration.
- //
- // See func Dial of the standard library for the supported networks.
- 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":
- return unixStrmDgramEnabled()
- 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.
- 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.
- //
- // 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
- }
|