1
2
3
4
5
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
57
58 out, _ := exec.Command("oslevel", "-s").Output()
59 if len(out) >= len("7200-XX-ZZ-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
75
76 func SupportsIPv4() bool {
77 stackOnce.Do(probeStack)
78 return ipv4Enabled
79 }
80
81
82
83 func SupportsIPv6() bool {
84 stackOnce.Do(probeStack)
85 return ipv6Enabled
86 }
87
88
89
90 func SupportsRawSocket() bool {
91 stackOnce.Do(probeStack)
92 return rawSocketSess
93 }
94
95
96
97
98
99 func TestableNetwork(network string) bool {
100 ss := strings.Split(network, ":")
101 switch ss[0] {
102 case "ip+nopriv":
103
104
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
141
142 func TestableAddress(network, address string) bool {
143 switch ss := strings.Split(network, ":"); ss[0] {
144 case "unix", "unixgram", "unixpacket":
145
146 if address[0] == '@' && runtime.GOOS != "linux" {
147 return false
148 }
149 }
150 return true
151 }
152
153
154
155
156
157
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
189
190
191
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
223
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
240
241
242
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
260
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
275
276
277
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() {
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 }
346
View as plain text