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