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 ipv6Enabled bool
26 unStrmDgramEnabled bool
27 rawSocketSess bool
28
29 aLongTimeAgo = time.Unix(233431200, 0)
30 neverTimeout = time.Time{}
31
32 errNoAvailableInterface = errors.New("no available interface")
33 errNoAvailableAddress = errors.New("no available address")
34 )
35
36 func probeStack() {
37 if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
38 ln.Close()
39 ipv4Enabled = true
40 }
41 if ln, err := net.Listen("tcp6", "[::1]:0"); err == nil {
42 ln.Close()
43 ipv6Enabled = true
44 }
45 rawSocketSess = supportsRawSocket()
46 switch runtime.GOOS {
47 case "aix":
48
49
50 out, _ := exec.Command("oslevel", "-s").Output()
51 if len(out) >= len("7200-XX-ZZ-YYMM") {
52 ver := string(out[:4])
53 tl, _ := strconv.Atoi(string(out[5:7]))
54 unStrmDgramEnabled = ver > "7200" || (ver == "7200" && tl >= 2)
55 }
56 default:
57 unStrmDgramEnabled = true
58 }
59 }
60
61 func unixStrmDgramEnabled() bool {
62 stackOnce.Do(probeStack)
63 return unStrmDgramEnabled
64 }
65
66
67
68 func SupportsIPv4() bool {
69 stackOnce.Do(probeStack)
70 return ipv4Enabled
71 }
72
73
74
75 func SupportsIPv6() bool {
76 stackOnce.Do(probeStack)
77 return ipv6Enabled
78 }
79
80
81
82 func SupportsRawSocket() bool {
83 stackOnce.Do(probeStack)
84 return rawSocketSess
85 }
86
87
88
89
90
91 func TestableNetwork(network string) bool {
92 ss := strings.Split(network, ":")
93 switch ss[0] {
94 case "ip+nopriv":
95
96
97 switch runtime.GOOS {
98 case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "windows":
99 return false
100 }
101 case "ip", "ip4", "ip6":
102 switch runtime.GOOS {
103 case "fuchsia", "hurd", "js", "nacl", "plan9":
104 return false
105 default:
106 if os.Getuid() != 0 {
107 return false
108 }
109 }
110 case "unix", "unixgram":
111 switch runtime.GOOS {
112 case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "windows":
113 return false
114 case "aix":
115 return unixStrmDgramEnabled()
116 }
117 case "unixpacket":
118 switch runtime.GOOS {
119 case "aix", "android", "fuchsia", "hurd", "darwin", "ios", "js", "nacl", "plan9", "windows", "zos":
120 return false
121 case "netbsd":
122
123
124 if runtime.GOARCH == "386" {
125 return false
126 }
127 }
128 }
129 switch ss[0] {
130 case "tcp4", "udp4", "ip4":
131 return SupportsIPv4()
132 case "tcp6", "udp6", "ip6":
133 return SupportsIPv6()
134 }
135 return true
136 }
137
138
139
140 func TestableAddress(network, address string) bool {
141 switch ss := strings.Split(network, ":"); ss[0] {
142 case "unix", "unixgram", "unixpacket":
143
144 if address[0] == '@' && runtime.GOOS != "linux" {
145 return false
146 }
147 }
148 return true
149 }
150
151
152
153
154
155
156 func NewLocalListener(network string) (net.Listener, error) {
157 switch network {
158 case "tcp":
159 if SupportsIPv4() {
160 if ln, err := net.Listen("tcp4", "127.0.0.1:0"); err == nil {
161 return ln, nil
162 }
163 }
164 if SupportsIPv6() {
165 return net.Listen("tcp6", "[::1]:0")
166 }
167 case "tcp4":
168 if SupportsIPv4() {
169 return net.Listen("tcp4", "127.0.0.1:0")
170 }
171 case "tcp6":
172 if SupportsIPv6() {
173 return net.Listen("tcp6", "[::1]:0")
174 }
175 case "unix", "unixpacket":
176 path, err := LocalPath()
177 if err != nil {
178 return nil, err
179 }
180 return net.Listen(network, path)
181 }
182 return nil, fmt.Errorf("%s is not supported on %s/%s", network, runtime.GOOS, runtime.GOARCH)
183 }
184
185
186
187
188
189 func NewLocalPacketListener(network string) (net.PacketConn, error) {
190 switch network {
191 case "udp":
192 if SupportsIPv4() {
193 if c, err := net.ListenPacket("udp4", "127.0.0.1:0"); err == nil {
194 return c, nil
195 }
196 }
197 if SupportsIPv6() {
198 return net.ListenPacket("udp6", "[::1]:0")
199 }
200 case "udp4":
201 if SupportsIPv4() {
202 return net.ListenPacket("udp4", "127.0.0.1:0")
203 }
204 case "udp6":
205 if SupportsIPv6() {
206 return net.ListenPacket("udp6", "[::1]:0")
207 }
208 case "unixgram":
209 path, err := LocalPath()
210 if err != nil {
211 return nil, err
212 }
213 return net.ListenPacket(network, path)
214 }
215 return nil, fmt.Errorf("%s is not supported on %s/%s", network, runtime.GOOS, runtime.GOARCH)
216 }
217
218
219
220 func LocalPath() (string, error) {
221 f, err := ioutil.TempFile("", "go-nettest")
222 if err != nil {
223 return "", err
224 }
225 path := f.Name()
226 f.Close()
227 os.Remove(path)
228 return path, nil
229 }
230
231
232
233
234
235 func MulticastSource(network string, ifi *net.Interface) (net.IP, error) {
236 switch network {
237 case "ip", "ip4", "ip6":
238 default:
239 return nil, errNoAvailableAddress
240 }
241 if ifi == nil || ifi.Flags&net.FlagUp == 0 || ifi.Flags&net.FlagMulticast == 0 {
242 return nil, errNoAvailableAddress
243 }
244 ip, ok := hasRoutableIP(network, ifi)
245 if !ok {
246 return nil, errNoAvailableAddress
247 }
248 return ip, nil
249 }
250
251
252
253 func LoopbackInterface() (*net.Interface, error) {
254 ift, err := net.Interfaces()
255 if err != nil {
256 return nil, errNoAvailableInterface
257 }
258 for _, ifi := range ift {
259 if ifi.Flags&net.FlagLoopback != 0 && ifi.Flags&net.FlagUp != 0 {
260 return &ifi, nil
261 }
262 }
263 return nil, errNoAvailableInterface
264 }
265
266
267
268
269
270 func RoutedInterface(network string, flags net.Flags) (*net.Interface, error) {
271 switch network {
272 case "ip", "ip4", "ip6":
273 default:
274 return nil, errNoAvailableInterface
275 }
276 ift, err := net.Interfaces()
277 if err != nil {
278 return nil, errNoAvailableInterface
279 }
280 for _, ifi := range ift {
281 if ifi.Flags&flags != flags {
282 continue
283 }
284 if _, ok := hasRoutableIP(network, &ifi); !ok {
285 continue
286 }
287 return &ifi, nil
288 }
289 return nil, errNoAvailableInterface
290 }
291
292 func hasRoutableIP(network string, ifi *net.Interface) (net.IP, bool) {
293 ifat, err := ifi.Addrs()
294 if err != nil {
295 return nil, false
296 }
297 for _, ifa := range ifat {
298 switch ifa := ifa.(type) {
299 case *net.IPAddr:
300 if ip, ok := routableIP(network, ifa.IP); ok {
301 return ip, true
302 }
303 case *net.IPNet:
304 if ip, ok := routableIP(network, ifa.IP); ok {
305 return ip, true
306 }
307 }
308 }
309 return nil, false
310 }
311
312 func routableIP(network string, ip net.IP) (net.IP, bool) {
313 if !ip.IsLoopback() && !ip.IsLinkLocalUnicast() && !ip.IsGlobalUnicast() {
314 return nil, false
315 }
316 switch network {
317 case "ip4":
318 if ip := ip.To4(); ip != nil {
319 return ip, true
320 }
321 case "ip6":
322 if ip.IsLoopback() {
323 return nil, false
324 }
325 if ip := ip.To16(); ip != nil && ip.To4() == nil {
326 return ip, true
327 }
328 default:
329 if ip := ip.To4(); ip != nil {
330 return ip, true
331 }
332 if ip := ip.To16(); ip != nil {
333 return ip, true
334 }
335 }
336 return nil, false
337 }
338
View as plain text