diff --git a/src/pkg/net/dial_test.go b/src/pkg/net/dial_test.go index f130a116a1e..9196450c4eb 100644 --- a/src/pkg/net/dial_test.go +++ b/src/pkg/net/dial_test.go @@ -84,3 +84,34 @@ func TestDialTimeout(t *testing.T) { } } } + +func TestSelfConnect(t *testing.T) { + // Test that Dial does not honor self-connects. + // See the comment in DialTCP. + + // Find a port that would be used as a local address. + l, err := Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + c, err := Dial("tcp", l.Addr().String()) + if err != nil { + t.Fatal(err) + } + addr := c.LocalAddr().String() + c.Close() + l.Close() + + // Try to connect to that address repeatedly. + n := 100000 + if testing.Short() { + n = 1000 + } + for i := 0; i < n; i++ { + c, err := Dial("tcp", addr) + if err == nil { + c.Close() + t.Errorf("#%d: Dial %q succeeded", i, addr) + } + } +} diff --git a/src/pkg/net/tcpsock_posix.go b/src/pkg/net/tcpsock_posix.go index 51a5d6f0ed8..200ce91566c 100644 --- a/src/pkg/net/tcpsock_posix.go +++ b/src/pkg/net/tcpsock_posix.go @@ -227,13 +227,43 @@ func DialTCP(net string, laddr, raddr *TCPAddr) (*TCPConn, error) { if raddr == nil { return nil, &OpError{"dial", net, nil, errMissingAddress} } + fd, err := internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) + + // TCP has a rarely used mechanism called a 'simultaneous connection' in + // which Dial("tcp", addr1, addr2) run on the machine at addr1 can + // connect to a simultaneous Dial("tcp", addr2, addr1) run on the machine + // at addr2, without either machine executing Listen. If laddr == nil, + // it means we want the kernel to pick an appropriate originating local + // address. Some Linux kernels cycle blindly through a fixed range of + // local ports, regardless of destination port. If a kernel happens to + // pick local port 50001 as the source for a Dial("tcp", "", "localhost:50001"), + // then the Dial will succeed, having simultaneously connected to itself. + // This can only happen when we are letting the kernel pick a port (laddr == nil) + // and when there is no listener for the destination address. + // It's hard to argue this is anything other than a kernel bug. If we + // see this happen, rather than expose the buggy effect to users, we + // close the fd and try again. If it happens twice more, we relent and + // use the result. See also: + // http://golang.org/issue/2690 + // http://stackoverflow.com/questions/4949858/ + for i := 0; i < 2 && err == nil && laddr == nil && selfConnect(fd); i++ { + fd.Close() + fd, err = internetSocket(net, laddr.toAddr(), raddr.toAddr(), syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP) + } + if err != nil { return nil, err } return newTCPConn(fd), nil } +func selfConnect(fd *netFD) bool { + l := fd.laddr.(*TCPAddr) + r := fd.raddr.(*TCPAddr) + return l.Port == r.Port && l.IP.Equal(r.IP) +} + // TCPListener is a TCP network listener. // Clients should typically use variables of type Listener // instead of assuming TCP.