reuseport: respect dialer timeout
Juan Batiz-Benet committed
Jan 22, 2015 at 05:23 UTC
8d08e1e3d6e5cffe7becd9c43e8cb92885ee019f
9 files changed
+406
-69
Godeps/Godeps.json
+1
-1
@@ -160,7 +160,7 @@
160
},
161
{
162
"ImportPath": "github.com/jbenet/go-reuseport",
163
- "Rev": "a2e454f12a99b8898c41f9dcebae6c35dc3efa3a"
163
+ "Rev": "6924153aded2d61c89a83c8f0738ed4e8df9191f"
164
},
165
{
166
"ImportPath": "github.com/jbenet/go-sockaddr/net",
Godeps/_workspace/src/github.com/jbenet/go-reuseport/addr.go
+12
@@ -18,3 +18,15 @@ func ResolveAddr(network, address string) (net.Addr, error) {
18
return net.ResolveUnixAddr(network, address)
19
}
20
}
21
+
22
+// conn is a struct that stores a raddr to get around:
23
+// * https://github.com/golang/go/issues/9661#issuecomment-71043147
24
+// * https://gist.github.com/jbenet/5c191d698fe9ec58c49d
25
+type conn struct {
26
+ net.Conn
27
+ raddr net.Addr
28
+}
29
+
30
+func (c *conn) RemoteAddr() net.Addr {
31
+ return c.raddr
32
+}
Godeps/_workspace/src/github.com/jbenet/go-reuseport/impl_unix.go
+70
-54
@@ -9,6 +9,7 @@ import (
9
"syscall"
10
"time"
11
12
+ poll "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-reuseport/poll"
13
sockaddrnet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-sockaddr/net"
14
)
15
@@ -30,20 +31,18 @@ func socket(family, socktype, protocol int) (fd int, err error) {
31
return -1, err
32
}
33
33
- // set non-blocking until after connect, because we cant poll using runtime :(
34
+ // cant set it until after connect
35
// if err = syscall.SetNonblock(fd, true); err != nil {
36
// syscall.Close(fd)
37
// return -1, err
38
// }
39
40
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, soReuseAddr, 1); err != nil {
40
- // fmt.Println("reuse addr failed")
41
syscall.Close(fd)
42
return -1, err
43
}
44
45
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, soReusePort, 1); err != nil {
46
- // fmt.Println("reuse port failed")
46
syscall.Close(fd)
47
return -1, err
48
}
@@ -51,7 +50,6 @@ func socket(family, socktype, protocol int) (fd int, err error) {
50
// set setLinger to 5 as reusing exact same (srcip:srcport, dstip:dstport)
51
// will otherwise fail on connect.
52
if err = setLinger(fd, 5); err != nil {
54
- // fmt.Println("linger failed")
53
syscall.Close(fd)
54
return -1, err
55
}
@@ -68,13 +66,13 @@ func dial(dialer net.Dialer, netw, addr string) (c net.Conn, err error) {
66
lprotocol int
67
rprotocol int
68
file *os.File
69
+ deadline time.Time
70
remoteSockaddr syscall.Sockaddr
71
localSockaddr syscall.Sockaddr
72
)
73
74
netAddr, err := ResolveAddr(netw, addr)
75
if err != nil {
77
- // fmt.Println("resolve addr failed")
76
return nil, err
77
}
78
@@ -84,6 +82,13 @@ func dial(dialer net.Dialer, netw, addr string) (c net.Conn, err error) {
82
return nil, ErrUnsupportedProtocol
83
}
84
85
+ switch {
86
+ case !dialer.Deadline.IsZero():
87
+ deadline = dialer.Deadline
88
+ case dialer.Timeout != 0:
89
+ deadline = time.Now().Add(dialer.Timeout)
90
+ }
91
+
92
localSockaddr = sockaddrnet.NetAddrToSockaddr(dialer.LocalAddr)
93
remoteSockaddr = sockaddrnet.NetAddrToSockaddr(netAddr)
94
@@ -109,18 +114,29 @@ func dial(dialer net.Dialer, netw, addr string) (c net.Conn, err error) {
114
// look at dialTCP in http://golang.org/src/net/tcpsock_posix.go .... !
115
// here we just try again 3 times.
116
for i := 0; i < 3; i++ {
117
+ if !deadline.IsZero() && deadline.Before(time.Now()) {
118
+ err = errTimeout
119
+ break
120
+ }
121
+
122
if fd, err = socket(rfamily, socktype, rprotocol); err != nil {
123
return nil, err
124
}
125
116
- if err = syscall.Bind(fd, localSockaddr); err != nil {
117
- // fmt.Println("bind failed")
126
+ if localSockaddr != nil {
127
+ if err = syscall.Bind(fd, localSockaddr); err != nil {
128
+ syscall.Close(fd)
129
+ return nil, err
130
+ }
131
+ }
132
+
133
+ if err = syscall.SetNonblock(fd, true); err != nil {
134
syscall.Close(fd)
135
return nil, err
136
}
121
- if err = connect(fd, remoteSockaddr); err != nil {
137
+
138
+ if err = connect(fd, remoteSockaddr, deadline); err != nil {
139
syscall.Close(fd)
123
- // fmt.Println("connect failed", localSockaddr, err)
140
continue // try again.
141
}
142
@@ -133,48 +149,40 @@ func dial(dialer net.Dialer, netw, addr string) (c net.Conn, err error) {
149
if rprotocol == syscall.IPPROTO_TCP {
150
// by default golang/net sets TCP no delay to true.
151
if err = setNoDelay(fd, true); err != nil {
136
- // fmt.Println("set no delay failed")
152
syscall.Close(fd)
153
return nil, err
154
}
155
}
156
142
- if err = syscall.SetNonblock(fd, true); err != nil {
157
+ // File Name get be nil
158
+ file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid()))
159
+ if c, err = net.FileConn(file); err != nil {
160
syscall.Close(fd)
161
return nil, err
162
}
163
147
- switch socktype {
148
- case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:
149
-
150
- // File Name get be nil
151
- file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid()))
152
- if c, err = net.FileConn(file); err != nil {
153
- // fmt.Println("fileconn failed")
154
- syscall.Close(fd)
155
- return nil, err
156
- }
157
-
158
- case syscall.SOCK_DGRAM:
159
-
160
- // File Name get be nil
161
- file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid()))
162
- if c, err = net.FileConn(file); err != nil {
163
- // fmt.Println("fileconn failed")
164
- syscall.Close(fd)
165
- return nil, err
166
- }
167
- }
168
-
164
if err = file.Close(); err != nil {
170
- // fmt.Println("file close failed")
165
syscall.Close(fd)
166
return nil, err
167
}
168
169
+ // c = wrapConnWithRemoteAddr(c, netAddr)
170
return c, err
171
}
172
173
+// there's a rare case where dial returns successfully but for some reason the
174
+// RemoteAddr is not yet set. So, since we know what raddr should be, we just
175
+// wrap it. This is not ideal in that sometimes getpeername() may return a
176
+// different addr. But until this is fixed, best way to do it.
177
+// * https://gist.github.com/jbenet/5c191d698fe9ec58c49d
178
+// * https://github.com/golang/go/issues/9661#issuecomment-71043147
179
+func wrapConnWithRemoteAddr(c net.Conn, raddr net.Addr) net.Conn {
180
+ if c.RemoteAddr() == nil {
181
+ return &conn{Conn: c, raddr: raddr}
182
+ }
183
+ return c // it's fine, no need to wrap.
184
+}
185
+
186
func listen(netw, addr string) (fd int, err error) {
187
var (
188
family int
@@ -185,7 +193,6 @@ func listen(netw, addr string) (fd int, err error) {
193
194
netAddr, err := ResolveAddr(netw, addr)
195
if err != nil {
188
- // fmt.Println("resolve addr failed")
196
return -1, err
197
}
198
@@ -205,7 +212,6 @@ func listen(netw, addr string) (fd int, err error) {
212
}
213
214
if err = syscall.Bind(fd, sockaddr); err != nil {
208
- // fmt.Println("bind failed")
215
syscall.Close(fd)
216
return -1, err
217
}
@@ -213,7 +219,6 @@ func listen(netw, addr string) (fd int, err error) {
219
if protocol == syscall.IPPROTO_TCP {
220
// by default golang/net sets TCP no delay to true.
221
if err = setNoDelay(fd, true); err != nil {
216
- // fmt.Println("set no delay failed")
222
syscall.Close(fd)
223
return -1, err
224
}
@@ -239,20 +244,17 @@ func listenStream(netw, addr string) (l net.Listener, err error) {
244
245
// Set backlog size to the maximum
246
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
242
- // fmt.Println("listen failed")
247
syscall.Close(fd)
248
return nil, err
249
}
250
251
file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid()))
252
if l, err = net.FileListener(file); err != nil {
249
- // fmt.Println("filelistener failed")
253
syscall.Close(fd)
254
return nil, err
255
}
256
257
if err = file.Close(); err != nil {
255
- // fmt.Println("file close failed")
258
syscall.Close(fd)
259
return nil, err
260
}
@@ -272,13 +274,11 @@ func listenPacket(netw, addr string) (p net.PacketConn, err error) {
274
275
file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid()))
276
if p, err = net.FilePacketConn(file); err != nil {
275
- // fmt.Println("filelistener failed")
277
syscall.Close(fd)
278
return nil, err
279
}
280
281
if err = file.Close(); err != nil {
281
- // fmt.Println("file close failed")
282
syscall.Close(fd)
283
return nil, err
284
}
@@ -298,13 +298,11 @@ func listenUDP(netw, addr string) (c net.Conn, err error) {
298
299
file = os.NewFile(uintptr(fd), filePrefix+strconv.Itoa(os.Getpid()))
300
if c, err = net.FileConn(file); err != nil {
301
- // fmt.Println("filelistener failed")
301
syscall.Close(fd)
302
return nil, err
303
}
304
305
if err = file.Close(); err != nil {
307
- // fmt.Println("file close failed")
306
syscall.Close(fd)
307
return nil, err
308
}
@@ -313,26 +311,36 @@ func listenUDP(netw, addr string) (c net.Conn, err error) {
311
}
312
313
// this is close to the connect() function inside stdlib/net
316
-func connect(fd int, ra syscall.Sockaddr) error {
314
+func connect(fd int, ra syscall.Sockaddr, deadline time.Time) error {
315
switch err := syscall.Connect(fd, ra); err {
316
case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
317
case nil, syscall.EISCONN:
318
+ if !deadline.IsZero() && deadline.Before(time.Now()) {
319
+ return errTimeout
320
+ }
321
return nil
322
default:
323
return err
324
}
325
325
- var err error
326
- start := time.Now()
326
+ poller, err := poll.New(fd)
327
+ if err != nil {
328
+ return err
329
+ }
330
+
331
for {
332
+ if err = poller.WaitWrite(deadline); err != nil {
333
+ return err
334
+ }
335
+
336
// if err := fd.pd.WaitWrite(); err != nil {
337
// return err
338
// }
339
// i'd use the above fd.pd.WaitWrite to poll io correctly, just like net sockets...
332
- // but of course, it uses fucking runtime_* functions that _cannot_ be used by
333
- // non-go-stdlib source... seriously guys, what kind of bullshit is that!?
340
+ // but of course, it uses the damn runtime_* functions that _cannot_ be used by
341
+ // non-go-stdlib source... seriously guys, this is not nice.
342
// we're relegated to using syscall.Select (what nightmare that is) or using
335
- // a simple but totally bogus time-based wait. garbage.
343
+ // a simple but totally bogus time-based wait. such garbage.
344
var nerr int
345
nerr, err = syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_ERROR)
346
if err != nil {
@@ -340,14 +348,22 @@ func connect(fd int, ra syscall.Sockaddr) error {
348
}
349
switch err = syscall.Errno(nerr); err {
350
case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
343
- if time.Now().Sub(start) > time.Second {
344
- return err
345
- }
346
- <-time.After(20 * time.Microsecond)
351
+ continue
352
case syscall.Errno(0), syscall.EISCONN:
353
+ if !deadline.IsZero() && deadline.Before(time.Now()) {
354
+ return errTimeout
355
+ }
356
return nil
357
default:
358
return err
359
}
360
}
361
}
362
+
363
+var errTimeout = &timeoutError{}
364
+
365
+type timeoutError struct{}
366
+
367
+func (e *timeoutError) Error() string { return "i/o timeout" }
368
+func (e *timeoutError) Timeout() bool { return true }
369
+func (e *timeoutError) Temporary() bool { return true }
Godeps/_workspace/src/github.com/jbenet/go-reuseport/interface.go
+12
-13
@@ -91,21 +91,20 @@ type Dialer struct {
91
// Returns a net.Conn created from a file discriptor for a socket
92
// with SO_REUSEPORT and SO_REUSEADDR option set.
93
func (d *Dialer) Dial(network, address string) (net.Conn, error) {
94
- c, err := dial(d.D, network, address)
95
- if err != nil {
96
- return nil, err
94
+ if !available() {
95
+ return nil, syscall.Errno(syscall.ENOPROTOOPT)
96
}
97
99
- // there's a rare case where dial returns successfully but for some reason the
100
- // RemoteAddr is not yet set. We wait here a while until it is, and if too long
101
- // passes, we fail. This is horrendous.
102
- for start := time.Now(); c.RemoteAddr() == nil; {
103
- if time.Now().Sub(start) > (time.Millisecond * 500) {
104
- c.Close()
105
- return nil, ErrReuseFailed
106
- }
98
+ return dial(d.D, network, address)
99
+}
100
108
- <-time.After(20 * time.Microsecond)
101
+func (d *Dialer) deadline(def time.Duration) time.Time {
102
+ switch {
103
+ case !d.D.Deadline.IsZero():
104
+ return d.D.Deadline
105
+ case d.D.Timeout != 0:
106
+ return time.Now().Add(d.D.Timeout)
107
+ default:
108
+ return time.Now().Add(def)
109
}
110
- return c, nil
110
}
Godeps/_workspace/src/github.com/jbenet/go-reuseport/poll/error.go
new
+9
@@ -0,0 +1,9 @@
1
+package poll
2
+
3
+var errTimeout = &timeoutError{}
4
+
5
+type timeoutError struct{}
6
+
7
+func (e *timeoutError) Error() string { return "i/o timeout" }
8
+func (e *timeoutError) Timeout() bool { return true }
9
+func (e *timeoutError) Temporary() bool { return true }
Godeps/_workspace/src/github.com/jbenet/go-reuseport/poll/poll_bsd.go
new
+59
@@ -0,0 +1,59 @@
1
+// +build darwin freebsd dragonfly netbsd openbsd
2
+
3
+package poll
4
+
5
+import (
6
+ "syscall"
7
+ "time"
8
+)
9
+
10
+type Poller struct {
11
+ kqfd int
12
+ event syscall.Kevent_t
13
+}
14
+
15
+func New(fd int) (p *Poller, err error) {
16
+ p = &Poller{}
17
+
18
+ p.kqfd, err = syscall.Kqueue()
19
+ if p.kqfd == -1 || err != nil {
20
+ return nil, err
21
+ }
22
+
23
+ p.event = syscall.Kevent_t{
24
+ Ident: uint64(fd),
25
+ Filter: syscall.EVFILT_WRITE,
26
+ Flags: syscall.EV_ADD | syscall.EV_ENABLE | syscall.EV_ONESHOT,
27
+ Fflags: 0,
28
+ Data: 0,
29
+ Udata: nil,
30
+ }
31
+ return p, nil
32
+}
33
+
34
+func (p *Poller) Close() error {
35
+ return syscall.Close(p.kqfd)
36
+}
37
+
38
+func (p *Poller) WaitWrite(deadline time.Time) error {
39
+
40
+ // setup timeout
41
+ var timeout *syscall.Timespec
42
+ if !deadline.IsZero() {
43
+ d := deadline.Sub(time.Now())
44
+ t := syscall.NsecToTimespec(d.Nanoseconds())
45
+ timeout = &t
46
+ }
47
+
48
+ // wait on kevent
49
+ events := make([]syscall.Kevent_t, 1)
50
+ n, err := syscall.Kevent(p.kqfd, []syscall.Kevent_t{p.event}, events, timeout)
51
+ if err != nil {
52
+ return err
53
+ }
54
+
55
+ if n < 1 {
56
+ return errTimeout
57
+ }
58
+ return nil
59
+}
Godeps/_workspace/src/github.com/jbenet/go-reuseport/poll/poll_linux.go
new
+51
@@ -0,0 +1,51 @@
1
+// +build linux
2
+
3
+package poll
4
+
5
+import (
6
+ "syscall"
7
+ "time"
8
+)
9
+
10
+type Poller struct {
11
+ epfd int
12
+ event syscall.EpollEvent
13
+ events [32]syscall.EpollEvent
14
+}
15
+
16
+func New(fd int) (p *Poller, err error) {
17
+ p = &Poller{}
18
+ if p.epfd, err = syscall.EpollCreate1(0); err != nil {
19
+ return nil, err
20
+ }
21
+
22
+ p.event.Events = syscall.EPOLLOUT
23
+ p.event.Fd = int32(fd)
24
+ if err = syscall.EpollCtl(p.epfd, syscall.EPOLL_CTL_ADD, fd, &p.event); err != nil {
25
+ p.Close()
26
+ return nil, err
27
+ }
28
+
29
+ return p, nil
30
+}
31
+
32
+func (p *Poller) Close() error {
33
+ return syscall.Close(p.epfd)
34
+}
35
+
36
+func (p *Poller) WaitWrite(deadline time.Time) error {
37
+ msec := -1
38
+ if !deadline.IsZero() {
39
+ d := deadline.Sub(time.Now())
40
+ msec = int(d.Nanoseconds() / 1000000) // ms!? omg...
41
+ }
42
+
43
+ n, err := syscall.EpollWait(p.epfd, p.events[:], msec)
44
+ if err != nil {
45
+ return err
46
+ }
47
+ if n < 1 {
48
+ return errTimeout
49
+ }
50
+ return nil
51
+}
Godeps/_workspace/src/github.com/jbenet/go-reuseport/poll/poll_unsupported.go
new
+11
@@ -0,0 +1,11 @@
1
+// +build windows plan9
2
+
3
+package poll
4
+
5
+import (
6
+ "errors"
7
+)
8
+
9
+func WaitWrite(fd int) error {
10
+ return errors.New("platform not supported")
11
+}
Godeps/_workspace/src/github.com/jbenet/go-reuseport/reuse_test.go
+181
-1
@@ -2,11 +2,15 @@ package reuseport
2
3
import (
4
"bytes"
5
+ "errors"
6
+ "fmt"
7
"io"
8
"net"
9
"os"
10
"strings"
11
+ "sync"
12
"testing"
13
+ "time"
14
)
15
16
func echo(c net.Conn) {
@@ -226,7 +230,7 @@ func TestStreamListenDialSamePort(t *testing.T) {
230
231
c1, err := Dial(network, l1.Addr().String(), l2.Addr().String())
232
if err != nil {
229
- t.Fatal(err)
233
+ t.Fatal(err, network, l1.Addr().String(), l2.Addr().String())
234
continue
235
}
236
defer c1.Close()
@@ -260,6 +264,120 @@ func TestStreamListenDialSamePort(t *testing.T) {
264
}
265
}
266
267
+func TestStreamListenDialSamePortStressManyMsgs(t *testing.T) {
268
+ testCases := [][]string{
269
+ []string{"tcp", "127.0.0.1:0"},
270
+ []string{"tcp4", "127.0.0.1:0"},
271
+ []string{"tcp6", "[::]:0"},
272
+ }
273
+
274
+ for _, tcase := range testCases {
275
+ subestStreamListenDialSamePortStress(t, tcase[0], tcase[1], 2, 1000)
276
+ }
277
+}
278
+
279
+func TestStreamListenDialSamePortStressManyNodes(t *testing.T) {
280
+ testCases := [][]string{
281
+ []string{"tcp", "127.0.0.1:0"},
282
+ []string{"tcp4", "127.0.0.1:0"},
283
+ []string{"tcp6", "[::]:0"},
284
+ }
285
+
286
+ for _, tcase := range testCases {
287
+ subestStreamListenDialSamePortStress(t, tcase[0], tcase[1], 50, 1)
288
+ }
289
+}
290
+
291
+func TestStreamListenDialSamePortStressManyMsgsManyNodes(t *testing.T) {
292
+ testCases := [][]string{
293
+ []string{"tcp", "127.0.0.1:0"},
294
+ []string{"tcp4", "127.0.0.1:0"},
295
+ []string{"tcp6", "[::]:0"},
296
+ }
297
+
298
+ for _, tcase := range testCases {
299
+ subestStreamListenDialSamePortStress(t, tcase[0], tcase[1], 50, 100)
300
+ }
301
+}
302
+
303
+func subestStreamListenDialSamePortStress(t *testing.T, network, addr string, nodes int, msgs int) {
304
+ t.Logf("testing %s:%s %d nodes %d msgs", network, addr, nodes, msgs)
305
+
306
+ var ls []net.Listener
307
+ for i := 0; i < nodes; i++ {
308
+ l, err := Listen(network, addr)
309
+ if err != nil {
310
+ t.Fatal(err)
311
+ }
312
+ defer l.Close()
313
+ go acceptAndEcho(l)
314
+ ls = append(ls, l)
315
+ t.Logf("listening %s", l.Addr())
316
+ }
317
+
318
+ // connect them all
319
+ var cs []net.Conn
320
+ for i := 0; i < nodes; i++ {
321
+ for j := 0; j < i; j++ {
322
+ if i == j {
323
+ continue // cannot do self.
324
+ }
325
+
326
+ ia := ls[i].Addr().String()
327
+ ja := ls[j].Addr().String()
328
+ c, err := Dial(network, ia, ja)
329
+ if err != nil {
330
+ t.Fatal(network, ia, ja, err)
331
+ }
332
+ defer c.Close()
333
+ cs = append(cs, c)
334
+ t.Logf("dialed %s --> %s", c.LocalAddr(), c.RemoteAddr())
335
+ }
336
+ }
337
+
338
+ errs := make(chan error)
339
+
340
+ send := func(c net.Conn, buf []byte) {
341
+ if _, err := c.Write(buf); err != nil {
342
+ errs <- err
343
+ }
344
+ }
345
+
346
+ recv := func(c net.Conn, buf []byte) {
347
+ buf2 := make([]byte, len(buf))
348
+ if _, err := c.Read(buf2); err != nil {
349
+ errs <- err
350
+ }
351
+ if !bytes.Equal(buf, buf2) {
352
+ errs <- fmt.Errorf("recv failure: %s <--> %s -- %s %s", c.RemoteAddr(), c.LocalAddr(), buf, buf2)
353
+ }
354
+ }
355
+
356
+ t.Logf("sending %d msgs per conn", msgs)
357
+ go func() {
358
+ var wg sync.WaitGroup
359
+ for _, c := range cs {
360
+ wg.Add(1)
361
+ go func(c net.Conn) {
362
+ defer wg.Done()
363
+ for i := 0; i < msgs; i++ {
364
+ msg := []byte(fmt.Sprintf("message %d", i))
365
+ send(c, msg)
366
+ recv(c, msg)
367
+ }
368
+ }(c)
369
+ }
370
+ wg.Wait()
371
+ close(errs)
372
+ }()
373
+
374
+ for err := range errs {
375
+ if err != nil {
376
+ t.Error(err)
377
+ }
378
+ }
379
+}
380
+
381
func TestPacketListenDialSamePort(t *testing.T) {
382
383
any := [][]string{
@@ -343,6 +461,68 @@ func TestPacketListenDialSamePort(t *testing.T) {
461
}
462
}
463
464
+func TestDialRespectsTimeout(t *testing.T) {
465
+
466
+ testCases := [][]string{
467
+ []string{"tcp", "127.0.0.1:6780", "1.2.3.4:6781"},
468
+ []string{"tcp4", "127.0.0.1:6782", "1.2.3.4:6783"},
469
+ []string{"tcp6", "[::1]:6784", "[::2]:6785"},
470
+ }
471
+
472
+ timeout := 50 * time.Millisecond
473
+
474
+ for _, tcase := range testCases {
475
+ network := tcase[0]
476
+ laddr := tcase[1]
477
+ raddr := tcase[2]
478
+
479
+ // l, err := Listen(network, raddr)
480
+ // if err != nil {
481
+ // t.Error("without a listener it wont work")
482
+ // continue
483
+ // }
484
+ // defer l.Close()
485
+
486
+ nladdr, err := ResolveAddr(network, laddr)
487
+ if err != nil {
488
+ t.Error("failed to resolve addr", network, laddr, err)
489
+ continue
490
+ }
491
+ t.Log("testing", network, nladdr, raddr)
492
+
493
+ d := Dialer{
494
+ D: net.Dialer{
495
+ LocalAddr: nil,
496
+ Timeout: timeout,
497
+ },
498
+ }
499
+
500
+ errs := make(chan error)
501
+ go func() {
502
+ c, err := d.Dial(network, raddr)
503
+ if err == nil {
504
+ errs <- errors.New("should've not connected")
505
+ c.Close()
506
+ return
507
+ }
508
+ close(errs) // success!
509
+ }()
510
+
511
+ ErrDrain:
512
+ select {
513
+ case <-time.After(5 * time.Second):
514
+ t.Fatal("took too long")
515
+ case err, more := <-errs:
516
+ if !more {
517
+ break
518
+ }
519
+ t.Error(err)
520
+ goto ErrDrain
521
+ }
522
+
523
+ }
524
+}
525
+
526
func TestUnixNotSupported(t *testing.T) {
527
528
testCases := [][]string{