]> Cypherpunks repositories - gostls13.git/commitdiff
net: avoid TCP self-connect
authorRuss Cox <rsc@golang.org>
Mon, 13 Feb 2012 04:25:55 +0000 (23:25 -0500)
committerRuss Cox <rsc@golang.org>
Mon, 13 Feb 2012 04:25:55 +0000 (23:25 -0500)
Fixes #2690.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5650071

src/pkg/net/dial_test.go
src/pkg/net/tcpsock_posix.go

index f130a116a1e7f59af67dd1512ecce0fc1db2a0bf..9196450c4ebdb0fab478cad207d8ad4cb2b1105c 100644 (file)
@@ -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)
+               }
+       }
+}
index 51a5d6f0ed8240fee42cee03e7557ffa898bb48f..200ce91566c7b456e780167a89dee583f0c49fdd 100644 (file)
@@ -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.