]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Add test showing that reusing TCP ports isn't a good idea
authorMatt Joiner <anacrolix@gmail.com>
Fri, 28 Apr 2023 11:27:18 +0000 (21:27 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Tue, 9 May 2023 05:45:52 +0000 (15:45 +1000)
reuse_test.go [new file with mode: 0644]

diff --git a/reuse_test.go b/reuse_test.go
new file mode 100644 (file)
index 0000000..3adf002
--- /dev/null
@@ -0,0 +1,70 @@
+package torrent
+
+import (
+       "context"
+       "errors"
+       "net"
+       "sync/atomic"
+       "syscall"
+       "testing"
+
+       "github.com/anacrolix/log"
+
+       qt "github.com/frankban/quicktest"
+)
+
+// Show that multiple connections from the same local TCP port to the same remote port will fail.
+func TestTcpPortReuseIsABadIdea(t *testing.T) {
+       remote, err := net.Listen("tcp", "localhost:0")
+       c := qt.New(t)
+       c.Assert(err, qt.IsNil)
+       defer remote.Close()
+       dialer := net.Dialer{}
+       dialer.Control = func(network, address string, c syscall.RawConn) (err error) {
+               return c.Control(func(fd uintptr) {
+                       err = setReusePortSockOpts(fd)
+               })
+       }
+       first, err := dialer.Dial("tcp", remote.Addr().String())
+       c.Assert(err, qt.IsNil)
+       defer first.Close()
+       dialer.LocalAddr = first.LocalAddr()
+       _, err = dialer.Dial("tcp", remote.Addr().String())
+       c.Assert(errors.Is(err, syscall.EADDRINUSE), qt.IsTrue)
+}
+
+// Show that multiple connections from the same local utp socket to the same remote port will
+// succeed. This is necessary for ut_holepunch to work.
+func TestUtpLocalPortIsReusable(t *testing.T) {
+       const network = "udp"
+       c := qt.New(t)
+       remote, err := NewUtpSocket(network, "localhost:0", nil, log.Default)
+       c.Assert(err, qt.IsNil)
+       defer remote.Close()
+       var remoteAccepts int32
+       go func() {
+               for {
+                       c, err := remote.Accept()
+                       if err != nil {
+                               if atomic.LoadInt32(&remoteAccepts) != 2 {
+                                       t.Logf("error accepting on remote: %v", err)
+                               }
+                               break
+                       }
+                       // This is not a leak, bugger off.
+                       defer c.Close()
+                       atomic.AddInt32(&remoteAccepts, 1)
+               }
+       }()
+       local, err := NewUtpSocket(network, "localhost:0", nil, log.Default)
+       c.Assert(err, qt.IsNil)
+       defer local.Close()
+       first, err := local.DialContext(context.Background(), network, remote.Addr().String())
+       c.Assert(err, qt.IsNil)
+       defer first.Close()
+       second, err := local.DialContext(context.Background(), network, remote.Addr().String())
+       c.Assert(err, qt.IsNil)
+       defer second.Close()
+       remote.Close()
+       c.Assert(atomic.LoadInt32(&remoteAccepts), qt.Equals, int32(2))
+}