github.com/anacrolix/torrent@v1.61.0/client_test.go (about) 1 package torrent 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 "io" 8 "math/rand" 9 "net" 10 "net/netip" 11 "os" 12 "path/filepath" 13 "reflect" 14 "testing" 15 "testing/iotest" 16 "time" 17 18 "github.com/anacrolix/dht/v2" 19 "github.com/anacrolix/log" 20 "github.com/anacrolix/missinggo/v2" 21 "github.com/anacrolix/missinggo/v2/filecache" 22 "github.com/go-quicktest/qt" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/require" 25 26 "github.com/anacrolix/torrent/bencode" 27 "github.com/anacrolix/torrent/internal/testutil" 28 "github.com/anacrolix/torrent/iplist" 29 "github.com/anacrolix/torrent/metainfo" 30 "github.com/anacrolix/torrent/storage" 31 ) 32 33 func TestClientDefault(t *testing.T) { 34 cl, err := NewClient(TestingConfig(t)) 35 require.NoError(t, err) 36 require.Empty(t, cl.Close()) 37 } 38 39 func TestClientNilConfig(t *testing.T) { 40 // The default config will put crap in the working directory. 41 origDir, _ := os.Getwd() 42 defer os.Chdir(origDir) 43 os.Chdir(t.TempDir()) 44 cl, err := NewClient(nil) 45 require.NoError(t, err) 46 require.Empty(t, cl.Close()) 47 } 48 49 func TestAddDropTorrent(t *testing.T) { 50 cl, err := NewClient(TestingConfig(t)) 51 require.NoError(t, err) 52 defer cl.Close() 53 dir, mi := testutil.GreetingTestTorrent() 54 defer os.RemoveAll(dir) 55 tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) 56 require.NoError(t, err) 57 assert.True(t, new) 58 tt.SetMaxEstablishedConns(0) 59 tt.SetMaxEstablishedConns(1) 60 tt.Drop() 61 } 62 63 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) { 64 // TODO? 65 t.SkipNow() 66 } 67 68 func TestAddTorrentNoUsableURLs(t *testing.T) { 69 // TODO? 70 t.SkipNow() 71 } 72 73 func TestAddPeersToUnknownTorrent(t *testing.T) { 74 // TODO? 75 t.SkipNow() 76 } 77 78 func TestPieceHashSize(t *testing.T) { 79 assert.Equal(t, 20, pieceHash.Size()) 80 } 81 82 func TestTorrentInitialState(t *testing.T) { 83 dir, mi := testutil.GreetingTestTorrent() 84 defer os.RemoveAll(dir) 85 var cl Client 86 cl.init(TestingConfig(t)) 87 tor := cl.newTorrent( 88 mi.HashInfoBytes(), 89 storage.NewFileWithCompletion(t.TempDir(), storage.NewMapPieceCompletion()), 90 ) 91 tor.setChunkSize(2) 92 tor.cl.lock() 93 err := tor.setInfoBytesLocked(mi.InfoBytes) 94 tor.cl.unlock() 95 require.NoError(t, err) 96 require.Len(t, tor.pieces, 3) 97 tor.pendAllChunkSpecs(0) 98 tor.cl.lock() 99 assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0)) 100 tor.cl.unlock() 101 assert.EqualValues(t, ChunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize)) 102 } 103 104 func TestReducedDialTimeout(t *testing.T) { 105 cfg := NewDefaultClientConfig() 106 for _, _case := range []struct { 107 Max time.Duration 108 HalfOpenLimit int 109 PendingPeers int 110 ExpectedReduced time.Duration 111 }{ 112 {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout}, 113 {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout}, 114 {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout}, 115 {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2}, 116 {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3}, 117 {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101}, 118 } { 119 reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers) 120 expected := _case.ExpectedReduced 121 if expected < cfg.MinDialTimeout { 122 expected = cfg.MinDialTimeout 123 } 124 if reduced != expected { 125 t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced) 126 } 127 } 128 } 129 130 func TestAddDropManyTorrents(t *testing.T) { 131 cl, err := NewClient(TestingConfig(t)) 132 require.NoError(t, err) 133 defer cl.Close() 134 for i := range 1000 { 135 var opts AddTorrentOpts 136 binary.PutVarint(opts.InfoHash[:], int64(i+1)) 137 tt, new := cl.AddTorrentOpt(opts) 138 assert.True(t, new) 139 defer tt.Drop() 140 } 141 } 142 143 func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl { 144 return storage.NewResourcePiecesOpts( 145 fc.AsResourceProvider(), 146 storage.ResourcePiecesOpts{ 147 LeaveIncompleteChunks: true, 148 }, 149 ) 150 } 151 152 func TestMergingTrackersByAddingSpecs(t *testing.T) { 153 cl, err := NewClient(TestingConfig(t)) 154 require.NoError(t, err) 155 defer cl.Close() 156 spec := TorrentSpec{} 157 rand.Read(spec.InfoHash[:]) 158 T, new, _ := cl.AddTorrentSpec(&spec) 159 if !new { 160 t.FailNow() 161 } 162 spec.Trackers = [][]string{{"http://a"}, {"udp://b"}} 163 _, new, _ = cl.AddTorrentSpec(&spec) 164 assert.False(t, new) 165 assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.announceList) 166 // Because trackers are disabled in TestingConfig. 167 assert.EqualValues(t, 0, len(T.trackerAnnouncers)) 168 } 169 170 // We read from a piece which is marked completed, but is missing data. 171 func TestCompletedPieceWrongSize(t *testing.T) { 172 cfg := TestingConfig(t) 173 cfg.DefaultStorage = badStorage{} 174 cl, err := NewClient(cfg) 175 require.NoError(t, err) 176 defer cl.Close() 177 info := metainfo.Info{ 178 PieceLength: 15, 179 Pieces: make([]byte, 20), 180 Files: []metainfo.FileInfo{ 181 {Path: []string{"greeting"}, Length: 13}, 182 }, 183 } 184 b, err := bencode.Marshal(info) 185 require.NoError(t, err) 186 tt, new := cl.AddTorrentOpt(AddTorrentOpts{ 187 InfoBytes: b, 188 InfoHash: metainfo.HashBytes(b), 189 }) 190 defer tt.Drop() 191 assert.True(t, new) 192 r := tt.NewReader() 193 defer r.Close() 194 r.SetContext(t.Context()) 195 qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents)))) 196 } 197 198 func BenchmarkAddLargeTorrent(b *testing.B) { 199 cfg := TestingConfig(b) 200 cfg.DisableTCP = true 201 cfg.DisableUTP = true 202 cl, err := NewClient(cfg) 203 require.NoError(b, err) 204 defer cl.Close() 205 b.ReportAllocs() 206 for i := 0; i < b.N; i += 1 { 207 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent") 208 if err != nil { 209 b.Fatal(err) 210 } 211 t.Drop() 212 } 213 } 214 215 func TestResponsive(t *testing.T) { 216 seederDataDir, mi := testutil.GreetingTestTorrent() 217 defer os.RemoveAll(seederDataDir) 218 cfg := TestingConfig(t) 219 cfg.Seed = true 220 cfg.DataDir = seederDataDir 221 seeder, err := NewClient(cfg) 222 require.Nil(t, err) 223 defer seeder.Close() 224 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) 225 seederTorrent.VerifyData() 226 leecherDataDir := t.TempDir() 227 cfg = TestingConfig(t) 228 cfg.DataDir = leecherDataDir 229 leecher, err := NewClient(cfg) 230 require.Nil(t, err) 231 defer leecher.Close() 232 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) { 233 ret = TorrentSpecFromMetaInfo(mi) 234 ret.ChunkSize = 2 235 return 236 }()) 237 leecherTorrent.AddClientPeer(seeder) 238 reader := leecherTorrent.NewReader() 239 defer reader.Close() 240 reader.SetReadahead(0) 241 reader.SetResponsive() 242 b := make([]byte, 2) 243 _, err = reader.Seek(3, io.SeekStart) 244 require.NoError(t, err) 245 _, err = io.ReadFull(reader, b) 246 assert.Nil(t, err) 247 assert.EqualValues(t, "lo", string(b)) 248 _, err = reader.Seek(11, io.SeekStart) 249 require.NoError(t, err) 250 n, err := io.ReadFull(reader, b) 251 assert.Nil(t, err) 252 assert.EqualValues(t, 2, n) 253 assert.EqualValues(t, "d\n", string(b)) 254 } 255 256 // TestResponsive was the first test to fail if uTP is disabled and TCP sockets dial from the 257 // listening port. 258 func TestResponsiveTcpOnly(t *testing.T) { 259 seederDataDir, mi := testutil.GreetingTestTorrent() 260 defer os.RemoveAll(seederDataDir) 261 cfg := TestingConfig(t) 262 cfg.DisableUTP = true 263 cfg.Seed = true 264 cfg.DataDir = seederDataDir 265 seeder, err := NewClient(cfg) 266 require.Nil(t, err) 267 defer seeder.Close() 268 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) 269 seederTorrent.VerifyData() 270 leecherDataDir := t.TempDir() 271 cfg = TestingConfig(t) 272 cfg.DataDir = leecherDataDir 273 leecher, err := NewClient(cfg) 274 require.Nil(t, err) 275 defer leecher.Close() 276 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) { 277 ret = TorrentSpecFromMetaInfo(mi) 278 ret.ChunkSize = 2 279 return 280 }()) 281 leecherTorrent.AddClientPeer(seeder) 282 reader := leecherTorrent.NewReader() 283 defer reader.Close() 284 reader.SetReadahead(0) 285 reader.SetResponsive() 286 b := make([]byte, 2) 287 _, err = reader.Seek(3, io.SeekStart) 288 require.NoError(t, err) 289 _, err = io.ReadFull(reader, b) 290 assert.Nil(t, err) 291 assert.EqualValues(t, "lo", string(b)) 292 _, err = reader.Seek(11, io.SeekStart) 293 require.NoError(t, err) 294 n, err := io.ReadFull(reader, b) 295 assert.Nil(t, err) 296 assert.EqualValues(t, 2, n) 297 assert.EqualValues(t, "d\n", string(b)) 298 } 299 300 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) { 301 seederDataDir, mi := testutil.GreetingTestTorrent() 302 defer os.RemoveAll(seederDataDir) 303 cfg := TestingConfig(t) 304 cfg.Seed = true 305 cfg.DataDir = seederDataDir 306 seeder, err := NewClient(cfg) 307 require.Nil(t, err) 308 defer seeder.Close() 309 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) 310 seederTorrent.VerifyData() 311 leecherDataDir := t.TempDir() 312 cfg = TestingConfig(t) 313 cfg.DataDir = leecherDataDir 314 leecher, err := NewClient(cfg) 315 require.Nil(t, err) 316 defer leecher.Close() 317 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) { 318 ret = TorrentSpecFromMetaInfo(mi) 319 ret.ChunkSize = 2 320 return 321 }()) 322 leecherTorrent.AddClientPeer(seeder) 323 rdr := leecherTorrent.NewReader() 324 t.Cleanup(func() { rdr.Close() }) 325 rdr.SetReadahead(0) 326 rdr.SetResponsive() 327 b := make([]byte, 2) 328 _, err = rdr.Seek(3, io.SeekStart) 329 require.NoError(t, err) 330 _, err = io.ReadFull(rdr, b) 331 assert.Nil(t, err) 332 assert.EqualValues(t, "lo", string(b)) 333 _, err = rdr.Seek(11, io.SeekStart) 334 require.NoError(t, err) 335 leecherTorrent.Drop() 336 n, err := rdr.Read(b) 337 qt.Assert(t, qt.Equals(err, errTorrentClosed)) 338 assert.EqualValues(t, 0, n) 339 } 340 341 func TestDhtInheritBlocklist(t *testing.T) { 342 ipl := iplist.New(nil) 343 require.NotNil(t, ipl) 344 cfg := TestingConfig(t) 345 cfg.IPBlocklist = ipl 346 cfg.NoDHT = false 347 cl, err := NewClient(cfg) 348 require.NoError(t, err) 349 defer cl.Close() 350 numServers := 0 351 cl.eachDhtServer(func(s DhtServer) { 352 t.Log(s) 353 assert.Equal(t, ipl, s.(AnacrolixDhtServerWrapper).Server.IPBlocklist()) 354 numServers++ 355 }) 356 qt.Assert(t, qt.Not(qt.Equals(numServers, 0))) 357 } 358 359 // Check that stuff is merged in subsequent AddTorrentSpec for the same 360 // infohash. 361 func TestAddTorrentSpecMerging(t *testing.T) { 362 cl, err := NewClient(TestingConfig(t)) 363 require.NoError(t, err) 364 defer cl.Close() 365 dir, mi := testutil.GreetingTestTorrent() 366 defer os.RemoveAll(dir) 367 tt, new := cl.AddTorrentOpt(AddTorrentOpts{ 368 InfoHash: mi.HashInfoBytes(), 369 }) 370 require.True(t, new) 371 require.Nil(t, tt.Info()) 372 _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) 373 require.NoError(t, err) 374 require.False(t, new) 375 require.NotNil(t, tt.Info()) 376 } 377 378 func TestTorrentDroppedBeforeGotInfo(t *testing.T) { 379 dir, mi := testutil.GreetingTestTorrent() 380 os.RemoveAll(dir) 381 cl, _ := NewClient(TestingConfig(t)) 382 defer cl.Close() 383 tt, _ := cl.AddTorrentOpt(AddTorrentOpts{ 384 InfoHash: mi.HashInfoBytes(), 385 }) 386 tt.Drop() 387 assert.EqualValues(t, 0, len(cl.Torrents())) 388 select { 389 case <-tt.GotInfo(): 390 t.FailNow() 391 default: 392 } 393 } 394 395 func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) { 396 for i := 0; i < info.NumPieces(); i += 1 { 397 p := info.Piece(i) 398 ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0) 399 } 400 } 401 402 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) { 403 fileCacheDir := t.TempDir() 404 fileCache, err := filecache.NewCache(fileCacheDir) 405 require.NoError(t, err) 406 greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent() 407 defer os.RemoveAll(greetingDataTempDir) 408 filePieceStore := csf(fileCache) 409 info, err := greetingMetainfo.UnmarshalInfo() 410 require.NoError(t, err) 411 ih := greetingMetainfo.HashInfoBytes() 412 greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(context.Background(), &info, ih) 413 require.NoError(t, err) 414 writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents)) 415 // require.Equal(t, len(testutil.GreetingFileContents), written) 416 // require.NoError(t, err) 417 for i := 0; i < info.NumPieces(); i++ { 418 p := info.Piece(i) 419 if alreadyCompleted { 420 require.NoError(t, greetingData.Piece(p).MarkComplete()) 421 } 422 } 423 cfg := TestingConfig(t) 424 // TODO: Disable network option? 425 cfg.DisableTCP = true 426 cfg.DisableUTP = true 427 cfg.DefaultStorage = filePieceStore 428 cl, err := NewClient(cfg) 429 require.NoError(t, err) 430 defer cl.Close() 431 tt, err := cl.AddTorrent(greetingMetainfo) 432 require.NoError(t, err) 433 psrs := tt.PieceStateRuns() 434 assert.Len(t, psrs, 1) 435 assert.EqualValues(t, 3, psrs[0].Length) 436 assert.Equal(t, alreadyCompleted, psrs[0].Complete) 437 if alreadyCompleted { 438 r := tt.NewReader() 439 qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents)))) 440 } 441 } 442 443 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) { 444 testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage) 445 } 446 447 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) { 448 testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage) 449 } 450 451 func TestAddMetainfoWithNodes(t *testing.T) { 452 cfg := TestingConfig(t) 453 cfg.ListenHost = func(string) string { return "" } 454 cfg.NoDHT = false 455 cfg.DhtStartingNodes = func(string) dht.StartingNodesGetter { return func() ([]dht.Addr, error) { return nil, nil } } 456 // For now, we want to just jam the nodes into the table, without verifying them first. Also the 457 // DHT code doesn't support mixing secure and insecure nodes if security is enabled (yet). 458 // cfg.DHTConfig.NoSecurity = true 459 cl, err := NewClient(cfg) 460 require.NoError(t, err) 461 defer cl.Close() 462 sum := func() (ret int64) { 463 cl.eachDhtServer(func(s DhtServer) { 464 ret += s.Stats().(dht.ServerStats).OutboundQueriesAttempted 465 }) 466 return 467 } 468 assert.EqualValues(t, 0, sum()) 469 tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent") 470 require.NoError(t, err) 471 // Nodes are not added or exposed in Torrent's metainfo. We just randomly 472 // check if the announce-list is here instead. TODO: Add nodes. 473 assert.Len(t, tt.announceList, 5) 474 // There are 6 nodes in the torrent file. 475 for sum() != int64(6*len(cl.dhtServers)) { 476 time.Sleep(time.Millisecond) 477 } 478 } 479 480 type testDownloadCancelParams struct { 481 SetLeecherStorageCapacity bool 482 LeecherStorageCapacity int64 483 Cancel bool 484 } 485 486 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) { 487 greetingTempDir, mi := testutil.GreetingTestTorrent() 488 defer os.RemoveAll(greetingTempDir) 489 cfg := TestingConfig(t) 490 cfg.Seed = true 491 cfg.DataDir = greetingTempDir 492 seeder, err := NewClient(cfg) 493 require.NoError(t, err) 494 defer seeder.Close() 495 defer testutil.ExportStatusWriter(seeder, "s", t)() 496 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi)) 497 seederTorrent.VerifyData() 498 leecherDataDir := t.TempDir() 499 fc, err := filecache.NewCache(leecherDataDir) 500 require.NoError(t, err) 501 if ps.SetLeecherStorageCapacity { 502 fc.SetCapacity(ps.LeecherStorageCapacity) 503 } 504 cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider()) 505 cfg.DataDir = leecherDataDir 506 leecher, err := NewClient(cfg) 507 require.NoError(t, err) 508 defer leecher.Close() 509 defer testutil.ExportStatusWriter(leecher, "l", t)() 510 leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) { 511 ret = TorrentSpecFromMetaInfo(mi) 512 ret.ChunkSize = 2 513 return 514 }()) 515 require.NoError(t, err) 516 assert.True(t, new) 517 psc := leecherGreeting.SubscribePieceStateChanges() 518 defer psc.Close() 519 520 leecherGreeting.cl.lock() 521 leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces()) 522 if ps.Cancel { 523 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces(), "") 524 } 525 leecherGreeting.cl.unlock() 526 done := make(chan struct{}) 527 defer close(done) 528 go leecherGreeting.AddClientPeer(seeder) 529 completes := make(map[int]bool, 3) 530 expected := func() map[int]bool { 531 if ps.Cancel { 532 return map[int]bool{0: false, 1: false, 2: false} 533 } else { 534 return map[int]bool{0: true, 1: true, 2: true} 535 } 536 }() 537 for !reflect.DeepEqual(completes, expected) { 538 v := <-psc.Values 539 completes[v.Index] = v.Complete 540 } 541 } 542 543 func TestTorrentDownloadAll(t *testing.T) { 544 testDownloadCancel(t, testDownloadCancelParams{}) 545 } 546 547 func TestTorrentDownloadAllThenCancel(t *testing.T) { 548 testDownloadCancel(t, testDownloadCancelParams{ 549 Cancel: true, 550 }) 551 } 552 553 // Ensure that it's an error for a peer to send an invalid have message. 554 func TestPeerInvalidHave(t *testing.T) { 555 cfg := TestingConfig(t) 556 cfg.DropMutuallyCompletePeers = false 557 cl, err := NewClient(cfg) 558 require.NoError(t, err) 559 defer cl.Close() 560 info := metainfo.Info{ 561 PieceLength: 1, 562 Pieces: make([]byte, 20), 563 Files: []metainfo.FileInfo{{Length: 1}}, 564 } 565 infoBytes, err := bencode.Marshal(info) 566 require.NoError(t, err) 567 tt, _new := cl.AddTorrentOpt(AddTorrentOpts{ 568 InfoBytes: infoBytes, 569 InfoHash: metainfo.HashBytes(infoBytes), 570 Storage: badStorage{}, 571 }) 572 assert.True(t, _new) 573 defer tt.Drop() 574 cn := &PeerConn{Peer: Peer{ 575 t: tt, 576 callbacks: &cfg.Callbacks, 577 }} 578 tt.conns[cn] = struct{}{} 579 cn.legacyPeerImpl = cn 580 cl.lock() 581 defer cl.unlock() 582 assert.NoError(t, cn.peerSentHave(0)) 583 assert.Error(t, cn.peerSentHave(1)) 584 } 585 586 func TestPieceCompletedInStorageButNotClient(t *testing.T) { 587 greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent() 588 defer os.RemoveAll(greetingTempDir) 589 cfg := TestingConfig(t) 590 cfg.DataDir = greetingTempDir 591 seeder, err := NewClient(TestingConfig(t)) 592 require.NoError(t, err) 593 defer seeder.Close() 594 _, new := seeder.AddTorrentOpt(AddTorrentOpts{ 595 InfoBytes: greetingMetainfo.InfoBytes, 596 InfoHash: greetingMetainfo.HashInfoBytes(), 597 }) 598 qt.Check(t, qt.IsNil(err)) 599 qt.Check(t, qt.IsTrue(new)) 600 } 601 602 // Check that when the listen port is 0, all the protocols listened on have 603 // the same port, and it isn't zero. 604 func TestClientDynamicListenPortAllProtocols(t *testing.T) { 605 cl, err := NewClient(TestingConfig(t)) 606 require.NoError(t, err) 607 defer cl.Close() 608 port := cl.LocalPort() 609 assert.NotEqual(t, 0, port) 610 cl.eachListener(func(s Listener) bool { 611 assert.Equal(t, port, missinggo.AddrPort(s.Addr())) 612 return true 613 }) 614 } 615 616 func TestClientDynamicListenTCPOnly(t *testing.T) { 617 cfg := TestingConfig(t) 618 cfg.DisableUTP = true 619 cfg.DisableTCP = false 620 cl, err := NewClient(cfg) 621 require.NoError(t, err) 622 defer cl.Close() 623 assert.NotEqual(t, 0, cl.LocalPort()) 624 } 625 626 func TestClientDynamicListenUTPOnly(t *testing.T) { 627 cfg := TestingConfig(t) 628 cfg.DisableTCP = true 629 cfg.DisableUTP = false 630 cl, err := NewClient(cfg) 631 require.NoError(t, err) 632 defer cl.Close() 633 assert.NotEqual(t, 0, cl.LocalPort()) 634 } 635 636 func totalConns(tts []*Torrent) (ret int) { 637 for _, tt := range tts { 638 tt.cl.lock() 639 ret += len(tt.conns) 640 tt.cl.unlock() 641 } 642 return 643 } 644 645 func TestSetMaxEstablishedConn(t *testing.T) { 646 var tts []*Torrent 647 ih := testutil.GreetingMetaInfo().HashInfoBytes() 648 cfg := TestingConfig(t) 649 cfg.DisableAcceptRateLimiting = true 650 cfg.DropDuplicatePeerIds = true 651 for i := 0; i < 3; i += 1 { 652 cl, err := NewClient(cfg) 653 require.NoError(t, err) 654 defer cl.Close() 655 tt, _ := cl.AddTorrentInfoHash(ih) 656 tt.SetMaxEstablishedConns(2) 657 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)() 658 tts = append(tts, tt) 659 } 660 addPeers := func() { 661 for _, tt := range tts { 662 for _, _tt := range tts { 663 // if tt != _tt { 664 tt.AddClientPeer(_tt.cl) 665 // } 666 } 667 } 668 } 669 waitTotalConns := func(num int) { 670 for totalConns(tts) != num { 671 addPeers() 672 time.Sleep(time.Millisecond) 673 } 674 } 675 addPeers() 676 waitTotalConns(6) 677 tts[0].SetMaxEstablishedConns(1) 678 waitTotalConns(4) 679 tts[0].SetMaxEstablishedConns(0) 680 waitTotalConns(2) 681 tts[0].SetMaxEstablishedConns(1) 682 addPeers() 683 waitTotalConns(4) 684 tts[0].SetMaxEstablishedConns(2) 685 addPeers() 686 waitTotalConns(6) 687 } 688 689 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given 690 // client, and returns a magnet link. 691 func makeMagnet(t *testing.T, cl *Client, dir, name string) string { 692 os.MkdirAll(dir, 0o770) 693 file, err := os.Create(filepath.Join(dir, name)) 694 require.NoError(t, err) 695 file.Write([]byte(name)) 696 file.Close() 697 mi := metainfo.MetaInfo{} 698 mi.SetDefaults() 699 info := metainfo.Info{PieceLength: 256 * 1024} 700 err = info.BuildFromFilePath(filepath.Join(dir, name)) 701 require.NoError(t, err) 702 mi.InfoBytes, err = bencode.Marshal(info) 703 require.NoError(t, err) 704 magnet := mi.Magnet(nil, &info).String() 705 tr, err := cl.AddTorrent(&mi) 706 require.NoError(t, err) 707 require.True(t, tr.Seeding()) 708 tr.VerifyData() 709 return magnet 710 } 711 712 // https://github.com/anacrolix/torrent/issues/114 713 func TestMultipleTorrentsWithEncryption(t *testing.T) { 714 testSeederLeecherPair( 715 t, 716 func(cfg *ClientConfig) { 717 cfg.HeaderObfuscationPolicy.Preferred = true 718 cfg.HeaderObfuscationPolicy.RequirePreferred = true 719 }, 720 func(cfg *ClientConfig) { 721 cfg.HeaderObfuscationPolicy.RequirePreferred = false 722 }, 723 ) 724 } 725 726 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the 727 // seeder config is done first. 728 func testSeederLeecherPair(t *testing.T, seeder, leecher func(*ClientConfig)) { 729 cfg := TestingConfig(t) 730 cfg.Seed = true 731 cfg.DataDir = filepath.Join(cfg.DataDir, "server") 732 os.Mkdir(cfg.DataDir, 0o755) 733 seeder(cfg) 734 server, err := NewClient(cfg) 735 require.NoError(t, err) 736 defer server.Close() 737 defer testutil.ExportStatusWriter(server, "s", t)() 738 magnet1 := makeMagnet(t, server, cfg.DataDir, "test1") 739 // Extra torrents are added to test the seeder having to match incoming obfuscated headers 740 // against more than one torrent. See issue #114 741 makeMagnet(t, server, cfg.DataDir, "test2") 742 for i := 0; i < 100; i++ { 743 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+3)) 744 } 745 cfg = TestingConfig(t) 746 cfg.DataDir = filepath.Join(cfg.DataDir, "client") 747 leecher(cfg) 748 client, err := NewClient(cfg) 749 require.NoError(t, err) 750 defer client.Close() 751 defer testutil.ExportStatusWriter(client, "c", t)() 752 tr, err := client.AddMagnet(magnet1) 753 require.NoError(t, err) 754 tr.AddClientPeer(server) 755 <-tr.GotInfo() 756 tr.DownloadAll() 757 client.WaitAll() 758 } 759 760 // This appears to be the situation with the S3 BitTorrent client. 761 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) { 762 // Leecher prefers obfuscation, but the seeder does not allow it. 763 testSeederLeecherPair( 764 t, 765 func(cfg *ClientConfig) { 766 cfg.HeaderObfuscationPolicy.Preferred = false 767 cfg.HeaderObfuscationPolicy.RequirePreferred = true 768 }, 769 func(cfg *ClientConfig) { 770 cfg.HeaderObfuscationPolicy.Preferred = true 771 cfg.HeaderObfuscationPolicy.RequirePreferred = false 772 }, 773 ) 774 } 775 776 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) { 777 // Leecher prefers no obfuscation, but the seeder enforces it. 778 testSeederLeecherPair( 779 t, 780 func(cfg *ClientConfig) { 781 cfg.HeaderObfuscationPolicy.Preferred = true 782 cfg.HeaderObfuscationPolicy.RequirePreferred = true 783 }, 784 func(cfg *ClientConfig) { 785 cfg.HeaderObfuscationPolicy.Preferred = false 786 cfg.HeaderObfuscationPolicy.RequirePreferred = false 787 }, 788 ) 789 } 790 791 func TestClientAddressInUse(t *testing.T) { 792 s, _ := NewUtpSocket("udp", "localhost:50007", nil, log.Default) 793 if s != nil { 794 defer s.Close() 795 } 796 cfg := TestingConfig(t).SetListenAddr("localhost:50007") 797 cfg.DisableUTP = false 798 cl, err := NewClient(cfg) 799 if err == nil { 800 assert.Nil(t, cl.Close()) 801 } 802 require.Error(t, err) 803 require.Nil(t, cl) 804 } 805 806 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) { 807 cc := TestingConfig(t) 808 cc.DisableUTP = true 809 cc.NoDHT = false 810 cl, err := NewClient(cc) 811 require.NoError(t, err) 812 defer cl.Close() 813 assert.NotEmpty(t, cl.DhtServers()) 814 } 815 816 func TestClientDisabledImplicitNetworksButDhtEnabled(t *testing.T) { 817 cfg := TestingConfig(t) 818 cfg.DisableTCP = true 819 cfg.DisableUTP = true 820 cfg.NoDHT = false 821 cl, err := NewClient(cfg) 822 require.NoError(t, err) 823 defer cl.Close() 824 assert.Empty(t, cl.listeners) 825 assert.NotEmpty(t, cl.DhtServers()) 826 } 827 828 func TestBadPeerIpPort(t *testing.T) { 829 for _, tc := range []struct { 830 title string 831 ip net.IP 832 port int 833 expectedOk bool 834 setup func(*Client) 835 }{ 836 {"empty both", nil, 0, true, func(*Client) {}}, 837 {"empty/nil ip", nil, 6666, true, func(*Client) {}}, 838 { 839 "empty port", 840 net.ParseIP("127.0.0.1/32"), 841 0, true, 842 func(*Client) {}, 843 }, 844 { 845 "in doppleganger addresses", 846 net.ParseIP("127.0.0.1/32"), 847 2322, 848 true, 849 func(cl *Client) { 850 cl.dopplegangerAddrs["10.0.0.1:2322"] = struct{}{} 851 }, 852 }, 853 { 854 "in IP block list", 855 net.ParseIP("10.0.0.1"), 856 2322, 857 true, 858 func(cl *Client) { 859 cl.ipBlockList = iplist.New([]iplist.Range{ 860 {First: net.ParseIP("10.0.0.1"), Last: net.ParseIP("10.0.0.255")}, 861 }) 862 }, 863 }, 864 { 865 "in bad peer IPs", 866 net.ParseIP("10.0.0.1"), 867 2322, 868 true, 869 func(cl *Client) { 870 ipAddr, ok := netip.AddrFromSlice(net.ParseIP("10.0.0.1")) 871 require.True(t, ok) 872 cl.badPeerIPs = map[netip.Addr]struct{}{} 873 cl.badPeerIPs[ipAddr] = struct{}{} 874 }, 875 }, 876 { 877 "good", 878 net.ParseIP("10.0.0.1"), 879 2322, 880 false, 881 func(cl *Client) {}, 882 }, 883 } { 884 t.Run(tc.title, func(t *testing.T) { 885 cfg := TestingConfig(t) 886 cfg.DisableTCP = true 887 cfg.DisableUTP = true 888 cfg.NoDHT = false 889 cl, err := NewClient(cfg) 890 require.NoError(t, err) 891 defer cl.Close() 892 893 tc.setup(cl) 894 require.Equal(t, tc.expectedOk, cl.badPeerIPPort(tc.ip, tc.port)) 895 }) 896 } 897 } 898 899 // https://github.com/anacrolix/torrent/issues/837 900 func TestClientConfigSetHandlerNotIgnored(t *testing.T) { 901 cfg := TestingConfig(t) 902 cfg.Logger.SetHandlers(log.DiscardHandler) 903 cl, err := NewClient(cfg) 904 qt.Assert(t, qt.IsNil(err)) 905 defer cl.Close() 906 qt.Assert(t, qt.HasLen(cl.logger.Handlers, 1)) 907 h := cl.logger.Handlers[0].(log.StreamHandler) 908 qt.Check(t, qt.Equals(h.W, io.Discard)) 909 } 910 911 func TestDroppedTorrentsNotReturned(t *testing.T) { 912 cl := newTestingClient(t) 913 tt, _ := cl.AddTorrentOpt(testingAddTorrentOpts) 914 tt1, ok := cl.Torrent(tt.InfoHash()) 915 qt.Check(t, qt.IsTrue(ok)) 916 qt.Check(t, qt.Equals(tt1, tt)) 917 qt.Check(t, qt.SliceContains(cl.Torrents(), tt)) 918 tt.Drop() 919 tt1, ok = cl.Torrent(tt.InfoHash()) 920 qt.Check(t, qt.IsFalse(ok)) 921 qt.Check(t, qt.HasLen(cl.Torrents(), 0)) 922 }