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