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 }