1
2
3
4
5
6 package nettest
7
8 import (
9 "errors"
10 "fmt"
11 "net"
12 "os"
13 "os/exec"
14 "runtime"
15 "strconv"
16 "strings"
17 "sync"
18 "time"
19 )
20
21 var (
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
37 func 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
56
57 out, _ := exec.Command("oslevel", "-s").Output()
58 if len(out) >= len("7200-XX-ZZ-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
68 func unixStrmDgramEnabled() bool {
69 stackOnce.Do(probeStack)
70 return unStrmDgramEnabled
71 }
72
73
74
75 func SupportsIPv4() bool {
76 stackOnce.Do(probeStack)
77 return ipv4Enabled
78 }
79
80
81
82 func SupportsIPv6() bool {
83 stackOnce.Do(probeStack)
84 return ipv6Enabled
85 }
86
87
88
89 func SupportsRawSocket() bool {
90 stackOnce.Do(probeStack)
91 return rawSocketSess
92 }
93
94
95
96
97
98 func TestableNetwork(network string) bool {
99 ss := strings.Split(network, ":")
100 switch ss[0] {
101 case "ip+nopriv":
102
103
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
140
141 func TestableAddress(network, address string) bool {
142 switch ss := strings.Split(network, ":"); ss[0] {
143 case "unix", "unixgram", "unixpacket":
144
145 if address[0] == '@' && runtime.GOOS != "linux" {
146 return false
147 }
148 }
149 return true
150 }
151
152
153
154
155
156
157 func 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
188
189
190
191 func 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
222
223 func 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
239
240
241
242 func 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
259
260 func 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
274
275
276
277 func 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
299 func 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
319 func 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() {
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
View as plain text