// 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 ( ) var ( stackOnce sync.Once ipv4Enabled bool canListenTCP4OnLoopback bool ipv6Enabled bool canListenTCP6OnLoopback 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 , := RoutedInterface("ip4", net.FlagUp); == nil { ipv4Enabled = true } if , := net.Listen("tcp4", "127.0.0.1:0"); == nil { .Close() canListenTCP4OnLoopback = true } if , := RoutedInterface("ip6", net.FlagUp); == nil { ipv6Enabled = true } if , := net.Listen("tcp6", "[::1]:0"); == nil { .Close() canListenTCP6OnLoopback = true } rawSocketSess = supportsRawSocket() switch runtime.GOOS { case "aix": // Unix network isn't properly working on AIX 7.2 with // Technical Level < 2. , := exec.Command("oslevel", "-s").Output() if len() >= len("7200-XX-ZZ-YYMM") { // AIX 7.2, Tech Level XX, Service Pack ZZ, date YYMM := string([:4]) , := strconv.Atoi(string([5:7])) unStrmDgramEnabled = > "7200" || ( == "7200" && >= 2) } default: unStrmDgramEnabled = true } } func unixStrmDgramEnabled() bool { stackOnce.Do(probeStack) return unStrmDgramEnabled } // SupportsIPv4 reports whether the platform supports IPv4 networking // functionality. func () bool { stackOnce.Do(probeStack) return ipv4Enabled } // SupportsIPv6 reports whether the platform supports IPv6 networking // functionality. func () bool { stackOnce.Do(probeStack) return ipv6Enabled } // SupportsRawSocket reports whether the current session is available // to use raw sockets. func () 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 ( string) bool { := strings.Split(, ":") switch [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", "ios", "js", "nacl", "plan9", "wasip1", "windows": return false } case "ip", "ip4", "ip6": switch runtime.GOOS { case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1": return false default: if os.Getuid() != 0 { return false } } case "unix", "unixgram": switch runtime.GOOS { case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "wasip1", "windows": return false case "aix": return unixStrmDgramEnabled() } case "unixpacket": switch runtime.GOOS { case "aix", "android", "fuchsia", "hurd", "darwin", "ios", "js", "nacl", "plan9", "wasip1", "windows", "zos": return false } } switch [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 (, string) bool { switch := strings.Split(, ":"); [0] { case "unix", "unixgram", "unixpacket": // Abstract unix domain sockets, a Linux-ism. if [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 ( string) (net.Listener, error) { stackOnce.Do(probeStack) switch { case "tcp": if canListenTCP4OnLoopback { if , := net.Listen("tcp4", "127.0.0.1:0"); == nil { return , nil } } if canListenTCP6OnLoopback { return net.Listen("tcp6", "[::1]:0") } case "tcp4": if canListenTCP4OnLoopback { return net.Listen("tcp4", "127.0.0.1:0") } case "tcp6": if canListenTCP6OnLoopback { return net.Listen("tcp6", "[::1]:0") } case "unix", "unixpacket": , := LocalPath() if != nil { return nil, } return net.Listen(, ) } return nil, fmt.Errorf("%s is not supported on %s/%s", , 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 ( string) (net.PacketConn, error) { stackOnce.Do(probeStack) switch { case "udp": if canListenTCP4OnLoopback { if , := net.ListenPacket("udp4", "127.0.0.1:0"); == nil { return , nil } } if canListenTCP6OnLoopback { return net.ListenPacket("udp6", "[::1]:0") } case "udp4": if canListenTCP4OnLoopback { return net.ListenPacket("udp4", "127.0.0.1:0") } case "udp6": if canListenTCP6OnLoopback { return net.ListenPacket("udp6", "[::1]:0") } case "unixgram": , := LocalPath() if != nil { return nil, } return net.ListenPacket(, ) } return nil, fmt.Errorf("%s is not supported on %s/%s", , runtime.GOOS, runtime.GOARCH) } // LocalPath returns a local path that can be used for Unix-domain // protocol testing. func () (string, error) { := "" if runtime.GOOS == "darwin" { = "/tmp" } , := os.CreateTemp(, "go-nettest") if != nil { return "", } := .Name() .Close() os.Remove() return , 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 ( string, *net.Interface) (net.IP, error) { switch { case "ip", "ip4", "ip6": default: return nil, errNoAvailableAddress } if == nil || .Flags&net.FlagUp == 0 || .Flags&net.FlagMulticast == 0 { return nil, errNoAvailableAddress } , := hasRoutableIP(, ) if ! { return nil, errNoAvailableAddress } return , nil } // LoopbackInterface returns an available logical network interface // for loopback test. func () (*net.Interface, error) { , := net.Interfaces() if != nil { return nil, errNoAvailableInterface } for , := range { if .Flags&net.FlagLoopback != 0 && .Flags&net.FlagUp != 0 { return &, 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 ( string, net.Flags) (*net.Interface, error) { switch { case "ip", "ip4", "ip6": default: return nil, errNoAvailableInterface } , := net.Interfaces() if != nil { return nil, errNoAvailableInterface } for , := range { if .Flags& != { continue } if , := hasRoutableIP(, &); ! { continue } return &, nil } return nil, errNoAvailableInterface } func hasRoutableIP( string, *net.Interface) (net.IP, bool) { , := .Addrs() if != nil { return nil, false } for , := range { switch ifa := .(type) { case *net.IPAddr: if , := routableIP(, .IP); { return , true } case *net.IPNet: if , := routableIP(, .IP); { return , true } } } return nil, false } func routableIP( string, net.IP) (net.IP, bool) { if !.IsLoopback() && !.IsLinkLocalUnicast() && !.IsGlobalUnicast() { return nil, false } switch { case "ip4": if := .To4(); != nil { return , true } case "ip6": if .IsLoopback() { // addressing scope of the loopback address depends on each implementation return nil, false } if := .To16(); != nil && .To4() == nil { return , true } default: if := .To4(); != nil { return , true } if := .To16(); != nil { return , true } } return nil, false }