github.com/anacrolix/torrent@v1.61.0/ut-holepunching_test.go (about) 1 package torrent 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "math/rand" 9 "net" 10 "os" 11 "sync" 12 "testing" 13 "testing/iotest" 14 "time" 15 16 "github.com/anacrolix/log" 17 "github.com/anacrolix/missinggo/v2/iter" 18 qt "github.com/go-quicktest/qt" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "golang.org/x/time/rate" 22 23 "github.com/anacrolix/torrent/internal/testutil" 24 ) 25 26 // Check that after completing leeching, a leecher transitions to a seeding 27 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher. 28 func TestHolepunchConnect(t *testing.T) { 29 greetingTempDir, mi := testutil.GreetingTestTorrent() 30 defer os.RemoveAll(greetingTempDir) 31 32 cfg := TestingConfig(t) 33 cfg.Seed = true 34 cfg.MaxAllocPeerRequestDataPerConn = 4 35 cfg.DataDir = greetingTempDir 36 cfg.DisablePEX = true 37 cfg.Debug = true 38 cfg.AcceptPeerConnections = false 39 // Listening, even without accepting, still means the leecher-leecher completes the dial to the 40 // seeder, and so it won't attempt to holepunch. 41 cfg.DisableTCP = true 42 // Ensure that responding to holepunch connects don't wait around for the dial limit. We also 43 // have to allow the initial connection to the leecher though, so it can rendezvous for us. 44 cfg.DialRateLimiter = rate.NewLimiter(0, 1) 45 cfg.Logger = cfg.Logger.WithContextText("seeder") 46 seeder, err := NewClient(cfg) 47 require.NoError(t, err) 48 defer seeder.Close() 49 defer testutil.ExportStatusWriter(seeder, "s", t)() 50 seederTorrent, ok, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) 51 require.NoError(t, err) 52 assert.True(t, ok) 53 seederTorrent.VerifyData() 54 55 cfg = TestingConfig(t) 56 cfg.Seed = true 57 cfg.DataDir = t.TempDir() 58 cfg.AlwaysWantConns = true 59 cfg.Logger = cfg.Logger.WithContextText("leecher") 60 // This way the leecher leecher will still try to use this peer as a relay, but won't be told 61 // about the seeder via PEX. 62 //cfg.DisablePEX = true 63 cfg.Debug = true 64 leecher, err := NewClient(cfg) 65 require.NoError(t, err) 66 defer leecher.Close() 67 defer testutil.ExportStatusWriter(leecher, "l", t)() 68 69 cfg = TestingConfig(t) 70 cfg.Seed = false 71 cfg.DataDir = t.TempDir() 72 cfg.MaxAllocPeerRequestDataPerConn = 4 73 cfg.Debug = true 74 cfg.NominalDialTimeout = time.Second 75 cfg.Logger = cfg.Logger.WithContextText("leecher-leecher") 76 //cfg.DisableUTP = true 77 leecherLeecher, _ := NewClient(cfg) 78 require.NoError(t, err) 79 defer leecherLeecher.Close() 80 defer testutil.ExportStatusWriter(leecherLeecher, "ll", t)() 81 leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) { 82 ret = TorrentSpecFromMetaInfo(mi) 83 ret.ChunkSize = 2 84 return 85 }()) 86 _ = leecherGreeting 87 require.NoError(t, err) 88 assert.True(t, ok) 89 llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) { 90 ret = TorrentSpecFromMetaInfo(mi) 91 ret.ChunkSize = 3 92 return 93 }()) 94 require.NoError(t, err) 95 assert.True(t, ok) 96 97 var wg sync.WaitGroup 98 wg.Add(1) 99 go func() { 100 defer wg.Done() 101 r := llg.NewReader() 102 defer r.Close() 103 qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents)))) 104 }() 105 go seederTorrent.AddClientPeer(leecher) 106 waitForConns(seederTorrent) 107 go llg.AddClientPeer(leecher) 108 waitForConns(llg) 109 time.Sleep(time.Second) 110 llg.cl.lock() 111 targetAddr := seeder.ListenAddrs()[0] 112 log.Printf("trying to initiate to %v", targetAddr) 113 initiateConn(outgoingConnOpts{ 114 peerInfo: PeerInfo{ 115 Addr: targetAddr, 116 }, 117 t: llg, 118 requireRendezvous: true, 119 skipHolepunchRendezvous: false, 120 HeaderObfuscationPolicy: llg.cl.config.HeaderObfuscationPolicy, 121 }, true) 122 llg.cl.unlock() 123 wg.Wait() 124 125 qt.Check(t, qt.Not(qt.HasLen(seeder.dialedSuccessfullyAfterHolepunchConnect, 0))) 126 qt.Check(t, qt.Not(qt.HasLen(leecherLeecher.probablyOnlyConnectedDueToHolepunch, 0))) 127 128 llClientStats := leecherLeecher.Stats() 129 qt.Check(t, qt.Not(qt.Equals(llClientStats.NumPeersUndialableWithoutHolepunch, 0))) 130 qt.Check(t, qt.Not(qt.Equals(llClientStats.NumPeersUndialableWithoutHolepunchDialedAfterHolepunchConnect, 0))) 131 qt.Check(t, qt.Not(qt.Equals(llClientStats.NumPeersProbablyOnlyConnectedDueToHolepunch, 0))) 132 } 133 134 func waitForConns(t *Torrent) { 135 t.cl.lock() 136 defer t.cl.unlock() 137 for { 138 for range t.conns { 139 return 140 } 141 t.cl.event.Wait() 142 } 143 } 144 145 // Show that dialling TCP will complete before the other side accepts. 146 func TestDialTcpNotAccepting(t *testing.T) { 147 l, err := net.Listen("tcp", "localhost:0") 148 qt.Check(t, qt.IsNil(err)) 149 defer l.Close() 150 dialedConn, err := net.Dial("tcp", l.Addr().String()) 151 qt.Assert(t, qt.IsNil(err)) 152 dialedConn.Close() 153 } 154 155 func TestTcpSimultaneousOpen(t *testing.T) { 156 const network = "tcp" 157 ctx := context.Background() 158 makeDialer := func(localPort int, remoteAddr string) func() (net.Conn, error) { 159 dialer := net.Dialer{ 160 LocalAddr: &net.TCPAddr{ 161 //IP: net.IPv6loopback, 162 Port: localPort, 163 }, 164 } 165 return func() (net.Conn, error) { 166 return dialer.DialContext(ctx, network, remoteAddr) 167 } 168 } 169 // I really hate doing this in unit tests, but we would need to pick apart Dialer to get 170 // perfectly synchronized simultaneous dials. 171 for range iter.N(10) { 172 first, second := randPortPair() 173 t.Logf("ports are %v and %v", first, second) 174 err := testSimultaneousOpen( 175 t.Cleanup, 176 makeDialer(first, fmt.Sprintf("localhost:%d", second)), 177 makeDialer(second, fmt.Sprintf("localhost:%d", first)), 178 ) 179 if err == nil { 180 return 181 } 182 // This proves that the connections are not the same. 183 if errors.Is(err, errMsgNotReceived) { 184 t.Fatal(err) 185 } 186 // Could be a timing issue, so try again. 187 t.Log(err) 188 } 189 // If we weren't able to get a simultaneous dial to occur, then we can't call it a failure. 190 t.Skip("couldn't synchronize dials") 191 } 192 193 func randIntInRange(low, high int) int { 194 return rand.Intn(high-low+1) + low 195 } 196 197 func randDynamicPort() int { 198 return randIntInRange(49152, 65535) 199 } 200 201 func randPortPair() (first int, second int) { 202 first = randDynamicPort() 203 for { 204 second = randDynamicPort() 205 if second != first { 206 return 207 } 208 } 209 } 210 211 func writeMsg(conn net.Conn) { 212 conn.Write([]byte(defaultMsg)) 213 // Writing must be closed so the reader will get EOF and stop reading. 214 conn.Close() 215 } 216 217 func readMsg(conn net.Conn) error { 218 msgBytes, err := io.ReadAll(conn) 219 if err != nil { 220 return err 221 } 222 msgStr := string(msgBytes) 223 if msgStr != defaultMsg { 224 return fmt.Errorf("read %q", msgStr) 225 } 226 return nil 227 } 228 229 var errMsgNotReceived = errors.New("msg not received in time") 230 231 // Runs two dialers simultaneously, then sends a message on one connection and check it reads from 232 // the other, thereby showing that both dials obtained endpoints to the same connection. 233 func testSimultaneousOpen( 234 cleanup func(func()), 235 firstDialer, secondDialer func() (net.Conn, error), 236 ) error { 237 errs := make(chan error) 238 var dialsDone sync.WaitGroup 239 const numDials = 2 240 dialsDone.Add(numDials) 241 signal := make(chan struct{}) 242 var dialersDone sync.WaitGroup 243 dialersDone.Add(numDials) 244 doDial := func( 245 dialer func() (net.Conn, error), 246 onSignal func(net.Conn), 247 ) { 248 defer dialersDone.Done() 249 conn, err := dialer() 250 dialsDone.Done() 251 errs <- err 252 if err != nil { 253 return 254 } 255 cleanup(func() { 256 conn.Close() 257 }) 258 <-signal 259 onSignal(conn) 260 //if err == nil { 261 // conn.Close() 262 //} 263 } 264 go doDial( 265 firstDialer, 266 func(conn net.Conn) { 267 writeMsg(conn) 268 errs <- nil 269 }, 270 ) 271 go doDial( 272 secondDialer, 273 func(conn net.Conn) { 274 gotMsg := make(chan error, 1) 275 go func() { 276 gotMsg <- readMsg(conn) 277 }() 278 select { 279 case err := <-gotMsg: 280 errs <- err 281 case <-time.After(time.Second): 282 errs <- errMsgNotReceived 283 } 284 }, 285 ) 286 dialsDone.Wait() 287 for range iter.N(numDials) { 288 err := <-errs 289 if err != nil { 290 return err 291 } 292 } 293 close(signal) 294 for range iter.N(numDials) { 295 err := <-errs 296 if err != nil { 297 return err 298 } 299 } 300 dialersDone.Wait() 301 return nil 302 } 303 304 const defaultMsg = "hello" 305 306 // Show that uTP doesn't implement simultaneous open. When two sockets dial each other, they both 307 // get separate connections. This means that holepunch connect may result in an accept (and dial) 308 // for one or both peers involved. 309 func TestUtpSimultaneousOpen(t *testing.T) { 310 t.Parallel() 311 const network = "udp" 312 ctx := context.Background() 313 newUtpSocket := func(addr string) utpSocket { 314 socket, err := NewUtpSocket( 315 network, 316 addr, 317 func(net.Addr) bool { 318 return false 319 }, 320 log.Default, 321 ) 322 qt.Assert(t, qt.IsNil(err)) 323 return socket 324 } 325 first := newUtpSocket("localhost:0") 326 defer first.Close() 327 second := newUtpSocket("localhost:0") 328 defer second.Close() 329 getDial := func(sock utpSocket, addr string) func() (net.Conn, error) { 330 return func() (net.Conn, error) { 331 return sock.DialContext(ctx, network, addr) 332 } 333 } 334 t.Logf("first addr is %v. second addr is %v", first.Addr().String(), second.Addr().String()) 335 for range iter.N(10) { 336 err := testSimultaneousOpen( 337 t.Cleanup, 338 getDial(first, second.Addr().String()), 339 getDial(second, first.Addr().String()), 340 ) 341 if err == nil { 342 t.Fatal("expected utp to fail simultaneous open") 343 } 344 if errors.Is(err, errMsgNotReceived) { 345 return 346 } 347 skipGoUtpDialIssue(t, err) 348 t.Log(err) 349 time.Sleep(time.Second) 350 } 351 t.FailNow() 352 } 353 354 func writeAndReadMsg(r, w net.Conn) error { 355 go writeMsg(w) 356 return readMsg(r) 357 } 358 359 func skipGoUtpDialIssue(t *testing.T, err error) { 360 if err.Error() == "timed out waiting for ack" { 361 t.Skip("anacrolix go utp implementation has issues. Use anacrolix/go-libutp by enabling CGO.") 362 } 363 } 364 365 // Show that dialling one socket and accepting from the other results in them having ends of the 366 // same connection. 367 func TestUtpDirectDialMsg(t *testing.T) { 368 t.Parallel() 369 const network = "udp4" 370 ctx := context.Background() 371 newUtpSocket := func(addr string) utpSocket { 372 socket, err := NewUtpSocket(network, addr, func(net.Addr) bool { 373 return false 374 }, log.Default) 375 qt.Assert(t, qt.IsNil(err)) 376 return socket 377 } 378 for range iter.N(10) { 379 err := func() error { 380 first := newUtpSocket("localhost:0") 381 defer first.Close() 382 second := newUtpSocket("localhost:0") 383 defer second.Close() 384 writer, err := first.DialContext(ctx, network, second.Addr().String()) 385 if err != nil { 386 return err 387 } 388 defer writer.Close() 389 reader, err := second.Accept() 390 qt.Assert(t, qt.IsNil(err)) 391 defer reader.Close() 392 return writeAndReadMsg(reader, writer) 393 }() 394 if err == nil { 395 return 396 } 397 skipGoUtpDialIssue(t, err) 398 t.Log(err) 399 time.Sleep(time.Second) 400 } 401 t.FailNow() 402 }