diff options
-rw-r--r-- | src/net/fd_unix.go | 40 | ||||
-rw-r--r-- | src/net/fd_windows.go | 15 | ||||
-rw-r--r-- | src/net/sock_posix.go | 15 | ||||
-rw-r--r-- | src/net/tcpsock_unix_test.go | 39 |
4 files changed, 77 insertions, 32 deletions
diff --git a/src/net/fd_unix.go b/src/net/fd_unix.go index 1122ee4dbe..352010c17d 100644 --- a/src/net/fd_unix.go +++ b/src/net/fd_unix.go @@ -63,7 +63,7 @@ func (fd *netFD) name() string { return fd.net + ":" + ls + "->" + rs } -func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (ret error) { +func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (rsa syscall.Sockaddr, ret error) { // Do not need to call fd.writeLock here, // because fd is not yet accessible to user, // so no concurrent operations are possible. @@ -72,14 +72,14 @@ func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (ret erro case nil, syscall.EISCONN: select { case <-ctx.Done(): - return mapErr(ctx.Err()) + return nil, mapErr(ctx.Err()) default: } if err := fd.pfd.Init(fd.net, true); err != nil { - return err + return nil, err } runtime.KeepAlive(fd) - return nil + return nil, nil case syscall.EINVAL: // On Solaris we can see EINVAL if the socket has // already been accepted and closed by the server. @@ -87,14 +87,14 @@ func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (ret erro // the socket will see EOF. For details and a test // case in C see https://golang.org/issue/6828. if runtime.GOOS == "solaris" { - return nil + return nil, nil } fallthrough default: - return os.NewSyscallError("connect", err) + return nil, os.NewSyscallError("connect", err) } if err := fd.pfd.Init(fd.net, true); err != nil { - return err + return nil, err } if deadline, _ := ctx.Deadline(); !deadline.IsZero() { fd.pfd.SetWriteDeadline(deadline) @@ -152,30 +152,28 @@ func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (ret erro if err := fd.pfd.WaitWrite(); err != nil { select { case <-ctx.Done(): - return mapErr(ctx.Err()) + return nil, mapErr(ctx.Err()) default: } - return err + return nil, err } nerr, err := getsockoptIntFunc(fd.pfd.Sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR) if err != nil { - return os.NewSyscallError("getsockopt", err) + return nil, os.NewSyscallError("getsockopt", err) } switch err := syscall.Errno(nerr); err { case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: - case syscall.Errno(0), syscall.EISCONN: - if runtime.GOOS != "darwin" { - return nil - } - // See golang.org/issue/14548. - // On Darwin, multiple connect system calls on - // a non-blocking socket never harm SO_ERROR. - switch err := connectFunc(fd.pfd.Sysfd, ra); err { - case nil, syscall.EISCONN: - return nil + case syscall.EISCONN: + return nil, nil + case syscall.Errno(0): + // The runtime poller can wake us up spuriously; + // see issues 14548 and 19289. Check that we are + // really connected; if not, wait again. + if rsa, err := syscall.Getpeername(fd.pfd.Sysfd); err == nil { + return rsa, nil } default: - return os.NewSyscallError("getsockopt", err) + return nil, os.NewSyscallError("getsockopt", err) } runtime.KeepAlive(fd) } diff --git a/src/net/fd_windows.go b/src/net/fd_windows.go index 0e5d37ab09..c2156b255e 100644 --- a/src/net/fd_windows.go +++ b/src/net/fd_windows.go @@ -65,12 +65,13 @@ func (fd *netFD) setAddr(laddr, raddr Addr) { runtime.SetFinalizer(fd, (*netFD).Close) } -func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) error { +// Always returns nil for connected peer address result. +func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (syscall.Sockaddr, error) { // Do not need to call fd.writeLock here, // because fd is not yet accessible to user, // so no concurrent operations are possible. if err := fd.init(); err != nil { - return err + return nil, err } if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() { fd.pfd.SetWriteDeadline(deadline) @@ -78,7 +79,7 @@ func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) error { } if !canUseConnectEx(fd.net) { err := connectFunc(fd.pfd.Sysfd, ra) - return os.NewSyscallError("connect", err) + return nil, os.NewSyscallError("connect", err) } // ConnectEx windows API requires an unconnected, previously bound socket. if la == nil { @@ -91,7 +92,7 @@ func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) error { panic("unexpected type in connect") } if err := syscall.Bind(fd.pfd.Sysfd, la); err != nil { - return os.NewSyscallError("bind", err) + return nil, os.NewSyscallError("bind", err) } } @@ -115,16 +116,16 @@ func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) error { if err := fd.pfd.ConnectEx(ra); err != nil { select { case <-ctx.Done(): - return mapErr(ctx.Err()) + return nil, mapErr(ctx.Err()) default: if _, ok := err.(syscall.Errno); ok { err = os.NewSyscallError("connectex", err) } - return err + return nil, err } } // Refresh socket properties. - return os.NewSyscallError("setsockopt", syscall.Setsockopt(fd.pfd.Sysfd, syscall.SOL_SOCKET, syscall.SO_UPDATE_CONNECT_CONTEXT, (*byte)(unsafe.Pointer(&fd.pfd.Sysfd)), int32(unsafe.Sizeof(fd.pfd.Sysfd)))) + return nil, os.NewSyscallError("setsockopt", syscall.Setsockopt(fd.pfd.Sysfd, syscall.SOL_SOCKET, syscall.SO_UPDATE_CONNECT_CONTEXT, (*byte)(unsafe.Pointer(&fd.pfd.Sysfd)), int32(unsafe.Sizeof(fd.pfd.Sysfd)))) } func (fd *netFD) Close() error { diff --git a/src/net/sock_posix.go b/src/net/sock_posix.go index 8985f8f23f..8cfc42eb7e 100644 --- a/src/net/sock_posix.go +++ b/src/net/sock_posix.go @@ -133,12 +133,13 @@ func (fd *netFD) dial(ctx context.Context, laddr, raddr sockaddr) error { } } } - var rsa syscall.Sockaddr + var rsa syscall.Sockaddr // remote address from the user + var crsa syscall.Sockaddr // remote address we actually connected to if raddr != nil { if rsa, err = raddr.sockaddr(fd.family); err != nil { return err } - if err := fd.connect(ctx, lsa, rsa); err != nil { + if crsa, err = fd.connect(ctx, lsa, rsa); err != nil { return err } fd.isConnected = true @@ -147,8 +148,16 @@ func (fd *netFD) dial(ctx context.Context, laddr, raddr sockaddr) error { return err } } + // Record the local and remote addresses from the actual socket. + // Get the local address by calling Getsockname. + // For the remote address, use + // 1) the one returned by the connect method, if any; or + // 2) the one from Getpeername, if it succeeds; or + // 3) the one passed to us as the raddr parameter. lsa, _ = syscall.Getsockname(fd.pfd.Sysfd) - if rsa, _ = syscall.Getpeername(fd.pfd.Sysfd); rsa != nil { + if crsa != nil { + fd.setAddr(fd.addrFunc()(lsa), fd.addrFunc()(crsa)) + } else if rsa, _ = syscall.Getpeername(fd.pfd.Sysfd); rsa != nil { fd.setAddr(fd.addrFunc()(lsa), fd.addrFunc()(rsa)) } else { fd.setAddr(fd.addrFunc()(lsa), raddr) diff --git a/src/net/tcpsock_unix_test.go b/src/net/tcpsock_unix_test.go index 2375fe24dc..3af1834455 100644 --- a/src/net/tcpsock_unix_test.go +++ b/src/net/tcpsock_unix_test.go @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin +// +build !plan9,!windows package net import ( + "context" + "internal/testenv" + "math/rand" "runtime" "sync" "syscall" @@ -77,3 +80,37 @@ func TestTCPSpuriousConnSetupCompletion(t *testing.T) { ln.Close() wg.Wait() } + +// Issue 19289. +// Test that a canceled Dial does not cause a subsequent Dial to succeed. +func TestTCPSpuriousConnSetupCompletionWithCancel(t *testing.T) { + if testenv.Builder() == "" { + testenv.MustHaveExternalNetwork(t) + } + t.Parallel() + const tries = 10000 + var wg sync.WaitGroup + wg.Add(tries * 2) + sem := make(chan bool, 5) + for i := 0; i < tries; i++ { + sem <- true + ctx, cancel := context.WithCancel(context.Background()) + go func() { + defer wg.Done() + time.Sleep(time.Duration(rand.Int63n(int64(5 * time.Millisecond)))) + cancel() + }() + go func(i int) { + defer wg.Done() + var dialer Dialer + // Try to connect to a real host on a port + // that it is not listening on. + _, err := dialer.DialContext(ctx, "tcp", "golang.org:3") + if err == nil { + t.Errorf("Dial to unbound port succeeded on attempt %d", i) + } + <-sem + }(i) + } + wg.Wait() +} |