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