]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Drop support for go 1.20
[btrtrc.git] / client_test.go
1 package torrent
2
3 import (
4         "encoding/binary"
5         "fmt"
6         "io"
7         "net"
8         "net/netip"
9         "os"
10         "path/filepath"
11         "reflect"
12         "testing"
13         "testing/iotest"
14         "time"
15
16         "github.com/anacrolix/dht/v2"
17         "github.com/anacrolix/log"
18         "github.com/anacrolix/missinggo/v2"
19         "github.com/anacrolix/missinggo/v2/filecache"
20         "github.com/frankban/quicktest"
21         qt "github.com/frankban/quicktest"
22         "github.com/stretchr/testify/assert"
23         "github.com/stretchr/testify/require"
24
25         "github.com/anacrolix/torrent/bencode"
26         "github.com/anacrolix/torrent/internal/testutil"
27         "github.com/anacrolix/torrent/iplist"
28         "github.com/anacrolix/torrent/metainfo"
29         "github.com/anacrolix/torrent/storage"
30 )
31
32 func TestClientDefault(t *testing.T) {
33         cl, err := NewClient(TestingConfig(t))
34         require.NoError(t, err)
35         require.Empty(t, cl.Close())
36 }
37
38 func TestClientNilConfig(t *testing.T) {
39         // The default config will put crap in the working directory.
40         origDir, _ := os.Getwd()
41         defer os.Chdir(origDir)
42         os.Chdir(t.TempDir())
43         cl, err := NewClient(nil)
44         require.NoError(t, err)
45         require.Empty(t, cl.Close())
46 }
47
48 func TestAddDropTorrent(t *testing.T) {
49         cl, err := NewClient(TestingConfig(t))
50         require.NoError(t, err)
51         defer cl.Close()
52         dir, mi := testutil.GreetingTestTorrent()
53         defer os.RemoveAll(dir)
54         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
55         require.NoError(t, err)
56         assert.True(t, new)
57         tt.SetMaxEstablishedConns(0)
58         tt.SetMaxEstablishedConns(1)
59         tt.Drop()
60 }
61
62 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
63         // TODO?
64         t.SkipNow()
65 }
66
67 func TestAddTorrentNoUsableURLs(t *testing.T) {
68         // TODO?
69         t.SkipNow()
70 }
71
72 func TestAddPeersToUnknownTorrent(t *testing.T) {
73         // TODO?
74         t.SkipNow()
75 }
76
77 func TestPieceHashSize(t *testing.T) {
78         assert.Equal(t, 20, pieceHash.Size())
79 }
80
81 func TestTorrentInitialState(t *testing.T) {
82         dir, mi := testutil.GreetingTestTorrent()
83         defer os.RemoveAll(dir)
84         var cl Client
85         cl.init(TestingConfig(t))
86         cl.initLogger()
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 := 0; i < 1000; i += 1 {
135                 var spec TorrentSpec
136                 binary.PutVarint(spec.InfoHash[:], int64(i))
137                 tt, new, err := cl.AddTorrentSpec(&spec)
138                 assert.NoError(t, err)
139                 assert.True(t, new)
140                 defer tt.Drop()
141         }
142 }
143
144 func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
145         return storage.NewResourcePiecesOpts(
146                 fc.AsResourceProvider(),
147                 storage.ResourcePiecesOpts{
148                         LeaveIncompleteChunks: true,
149                 },
150         )
151 }
152
153 func TestMergingTrackersByAddingSpecs(t *testing.T) {
154         cl, err := NewClient(TestingConfig(t))
155         require.NoError(t, err)
156         defer cl.Close()
157         spec := TorrentSpec{}
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.metainfo.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, err := cl.AddTorrentSpec(&TorrentSpec{
187                 InfoBytes: b,
188                 InfoHash:  metainfo.HashBytes(b),
189         })
190         require.NoError(t, err)
191         defer tt.Drop()
192         assert.True(t, new)
193         r := tt.NewReader()
194         defer r.Close()
195         quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil)
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         reader := leecherTorrent.NewReader()
324         defer reader.Close()
325         reader.SetReadahead(0)
326         reader.SetResponsive()
327         b := make([]byte, 2)
328         _, err = reader.Seek(3, io.SeekStart)
329         require.NoError(t, err)
330         _, err = io.ReadFull(reader, b)
331         assert.Nil(t, err)
332         assert.EqualValues(t, "lo", string(b))
333         _, err = reader.Seek(11, io.SeekStart)
334         require.NoError(t, err)
335         leecherTorrent.Drop()
336         n, err := reader.Read(b)
337         assert.EqualError(t, err, "torrent closed")
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         assert.EqualValues(t, 2, numServers)
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, err := cl.AddTorrentSpec(&TorrentSpec{
368                 InfoHash: mi.HashInfoBytes(),
369         })
370         require.NoError(t, err)
371         require.True(t, new)
372         require.Nil(t, tt.Info())
373         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
374         require.NoError(t, err)
375         require.False(t, new)
376         require.NotNil(t, tt.Info())
377 }
378
379 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
380         dir, mi := testutil.GreetingTestTorrent()
381         os.RemoveAll(dir)
382         cl, _ := NewClient(TestingConfig(t))
383         defer cl.Close()
384         tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
385                 InfoHash: mi.HashInfoBytes(),
386         })
387         tt.Drop()
388         assert.EqualValues(t, 0, len(cl.Torrents()))
389         select {
390         case <-tt.GotInfo():
391                 t.FailNow()
392         default:
393         }
394 }
395
396 func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
397         for i := 0; i < info.NumPieces(); i += 1 {
398                 p := info.Piece(i)
399                 ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
400         }
401 }
402
403 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
404         fileCacheDir := t.TempDir()
405         fileCache, err := filecache.NewCache(fileCacheDir)
406         require.NoError(t, err)
407         greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
408         defer os.RemoveAll(greetingDataTempDir)
409         filePieceStore := csf(fileCache)
410         info, err := greetingMetainfo.UnmarshalInfo()
411         require.NoError(t, err)
412         ih := greetingMetainfo.HashInfoBytes()
413         greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
414         require.NoError(t, err)
415         writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
416         // require.Equal(t, len(testutil.GreetingFileContents), written)
417         // require.NoError(t, err)
418         for i := 0; i < info.NumPieces(); i++ {
419                 p := info.Piece(i)
420                 if alreadyCompleted {
421                         require.NoError(t, greetingData.Piece(p).MarkComplete())
422                 }
423         }
424         cfg := TestingConfig(t)
425         // TODO: Disable network option?
426         cfg.DisableTCP = true
427         cfg.DisableUTP = true
428         cfg.DefaultStorage = filePieceStore
429         cl, err := NewClient(cfg)
430         require.NoError(t, err)
431         defer cl.Close()
432         tt, err := cl.AddTorrent(greetingMetainfo)
433         require.NoError(t, err)
434         psrs := tt.PieceStateRuns()
435         assert.Len(t, psrs, 1)
436         assert.EqualValues(t, 3, psrs[0].Length)
437         assert.Equal(t, alreadyCompleted, psrs[0].Complete)
438         if alreadyCompleted {
439                 r := tt.NewReader()
440                 quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil)
441         }
442 }
443
444 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
445         testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
446 }
447
448 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
449         testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
450 }
451
452 func TestAddMetainfoWithNodes(t *testing.T) {
453         cfg := TestingConfig(t)
454         cfg.ListenHost = func(string) string { return "" }
455         cfg.NoDHT = false
456         cfg.DhtStartingNodes = func(string) dht.StartingNodesGetter { return func() ([]dht.Addr, error) { return nil, nil } }
457         // For now, we want to just jam the nodes into the table, without verifying them first. Also the
458         // DHT code doesn't support mixing secure and insecure nodes if security is enabled (yet).
459         // cfg.DHTConfig.NoSecurity = true
460         cl, err := NewClient(cfg)
461         require.NoError(t, err)
462         defer cl.Close()
463         sum := func() (ret int64) {
464                 cl.eachDhtServer(func(s DhtServer) {
465                         ret += s.Stats().(dht.ServerStats).OutboundQueriesAttempted
466                 })
467                 return
468         }
469         assert.EqualValues(t, 0, sum())
470         tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
471         require.NoError(t, err)
472         // Nodes are not added or exposed in Torrent's metainfo. We just randomly
473         // check if the announce-list is here instead. TODO: Add nodes.
474         assert.Len(t, tt.metainfo.AnnounceList, 5)
475         // There are 6 nodes in the torrent file.
476         for sum() != int64(6*len(cl.dhtServers)) {
477                 time.Sleep(time.Millisecond)
478         }
479 }
480
481 type testDownloadCancelParams struct {
482         SetLeecherStorageCapacity bool
483         LeecherStorageCapacity    int64
484         Cancel                    bool
485 }
486
487 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
488         greetingTempDir, mi := testutil.GreetingTestTorrent()
489         defer os.RemoveAll(greetingTempDir)
490         cfg := TestingConfig(t)
491         cfg.Seed = true
492         cfg.DataDir = greetingTempDir
493         seeder, err := NewClient(cfg)
494         require.NoError(t, err)
495         defer seeder.Close()
496         defer testutil.ExportStatusWriter(seeder, "s", t)()
497         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
498         seederTorrent.VerifyData()
499         leecherDataDir := t.TempDir()
500         fc, err := filecache.NewCache(leecherDataDir)
501         require.NoError(t, err)
502         if ps.SetLeecherStorageCapacity {
503                 fc.SetCapacity(ps.LeecherStorageCapacity)
504         }
505         cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
506         cfg.DataDir = leecherDataDir
507         leecher, err := NewClient(cfg)
508         require.NoError(t, err)
509         defer leecher.Close()
510         defer testutil.ExportStatusWriter(leecher, "l", t)()
511         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
512                 ret = TorrentSpecFromMetaInfo(mi)
513                 ret.ChunkSize = 2
514                 return
515         }())
516         require.NoError(t, err)
517         assert.True(t, new)
518         psc := leecherGreeting.SubscribePieceStateChanges()
519         defer psc.Close()
520
521         leecherGreeting.cl.lock()
522         leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
523         if ps.Cancel {
524                 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces(), "")
525         }
526         leecherGreeting.cl.unlock()
527         done := make(chan struct{})
528         defer close(done)
529         go leecherGreeting.AddClientPeer(seeder)
530         completes := make(map[int]bool, 3)
531         expected := func() map[int]bool {
532                 if ps.Cancel {
533                         return map[int]bool{0: false, 1: false, 2: false}
534                 } else {
535                         return map[int]bool{0: true, 1: true, 2: true}
536                 }
537         }()
538         for !reflect.DeepEqual(completes, expected) {
539                 v := <-psc.Values
540                 completes[v.Index] = v.Complete
541         }
542 }
543
544 func TestTorrentDownloadAll(t *testing.T) {
545         testDownloadCancel(t, testDownloadCancelParams{})
546 }
547
548 func TestTorrentDownloadAllThenCancel(t *testing.T) {
549         testDownloadCancel(t, testDownloadCancelParams{
550                 Cancel: true,
551         })
552 }
553
554 // Ensure that it's an error for a peer to send an invalid have message.
555 func TestPeerInvalidHave(t *testing.T) {
556         cfg := TestingConfig(t)
557         cfg.DropMutuallyCompletePeers = false
558         cl, err := NewClient(cfg)
559         require.NoError(t, err)
560         defer cl.Close()
561         info := metainfo.Info{
562                 PieceLength: 1,
563                 Pieces:      make([]byte, 20),
564                 Files:       []metainfo.FileInfo{{Length: 1}},
565         }
566         infoBytes, err := bencode.Marshal(info)
567         require.NoError(t, err)
568         tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
569                 InfoBytes: infoBytes,
570                 InfoHash:  metainfo.HashBytes(infoBytes),
571                 Storage:   badStorage{},
572         })
573         require.NoError(t, err)
574         assert.True(t, _new)
575         defer tt.Drop()
576         cn := &PeerConn{Peer: Peer{
577                 t:         tt,
578                 callbacks: &cfg.Callbacks,
579         }}
580         tt.conns[cn] = struct{}{}
581         cn.peerImpl = cn
582         cl.lock()
583         defer cl.unlock()
584         assert.NoError(t, cn.peerSentHave(0))
585         assert.Error(t, cn.peerSentHave(1))
586 }
587
588 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
589         greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
590         defer os.RemoveAll(greetingTempDir)
591         cfg := TestingConfig(t)
592         cfg.DataDir = greetingTempDir
593         seeder, err := NewClient(TestingConfig(t))
594         require.NoError(t, err)
595         defer seeder.Close()
596         seeder.AddTorrentSpec(&TorrentSpec{
597                 InfoBytes: greetingMetainfo.InfoBytes,
598         })
599 }
600
601 // Check that when the listen port is 0, all the protocols listened on have
602 // the same port, and it isn't zero.
603 func TestClientDynamicListenPortAllProtocols(t *testing.T) {
604         cl, err := NewClient(TestingConfig(t))
605         require.NoError(t, err)
606         defer cl.Close()
607         port := cl.LocalPort()
608         assert.NotEqual(t, 0, port)
609         cl.eachListener(func(s Listener) bool {
610                 assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
611                 return true
612         })
613 }
614
615 func TestClientDynamicListenTCPOnly(t *testing.T) {
616         cfg := TestingConfig(t)
617         cfg.DisableUTP = true
618         cfg.DisableTCP = false
619         cl, err := NewClient(cfg)
620         require.NoError(t, err)
621         defer cl.Close()
622         assert.NotEqual(t, 0, cl.LocalPort())
623 }
624
625 func TestClientDynamicListenUTPOnly(t *testing.T) {
626         cfg := TestingConfig(t)
627         cfg.DisableTCP = true
628         cfg.DisableUTP = false
629         cl, err := NewClient(cfg)
630         require.NoError(t, err)
631         defer cl.Close()
632         assert.NotEqual(t, 0, cl.LocalPort())
633 }
634
635 func totalConns(tts []*Torrent) (ret int) {
636         for _, tt := range tts {
637                 tt.cl.lock()
638                 ret += len(tt.conns)
639                 tt.cl.unlock()
640         }
641         return
642 }
643
644 func TestSetMaxEstablishedConn(t *testing.T) {
645         var tts []*Torrent
646         ih := testutil.GreetingMetaInfo().HashInfoBytes()
647         cfg := TestingConfig(t)
648         cfg.DisableAcceptRateLimiting = true
649         cfg.DropDuplicatePeerIds = true
650         for i := 0; i < 3; i += 1 {
651                 cl, err := NewClient(cfg)
652                 require.NoError(t, err)
653                 defer cl.Close()
654                 tt, _ := cl.AddTorrentInfoHash(ih)
655                 tt.SetMaxEstablishedConns(2)
656                 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)()
657                 tts = append(tts, tt)
658         }
659         addPeers := func() {
660                 for _, tt := range tts {
661                         for _, _tt := range tts {
662                                 // if tt != _tt {
663                                 tt.AddClientPeer(_tt.cl)
664                                 // }
665                         }
666                 }
667         }
668         waitTotalConns := func(num int) {
669                 for totalConns(tts) != num {
670                         addPeers()
671                         time.Sleep(time.Millisecond)
672                 }
673         }
674         addPeers()
675         waitTotalConns(6)
676         tts[0].SetMaxEstablishedConns(1)
677         waitTotalConns(4)
678         tts[0].SetMaxEstablishedConns(0)
679         waitTotalConns(2)
680         tts[0].SetMaxEstablishedConns(1)
681         addPeers()
682         waitTotalConns(4)
683         tts[0].SetMaxEstablishedConns(2)
684         addPeers()
685         waitTotalConns(6)
686 }
687
688 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
689 // client, and returns a magnet link.
690 func makeMagnet(t *testing.T, cl *Client, dir, name string) string {
691         os.MkdirAll(dir, 0o770)
692         file, err := os.Create(filepath.Join(dir, name))
693         require.NoError(t, err)
694         file.Write([]byte(name))
695         file.Close()
696         mi := metainfo.MetaInfo{}
697         mi.SetDefaults()
698         info := metainfo.Info{PieceLength: 256 * 1024}
699         err = info.BuildFromFilePath(filepath.Join(dir, name))
700         require.NoError(t, err)
701         mi.InfoBytes, err = bencode.Marshal(info)
702         require.NoError(t, err)
703         magnet := mi.Magnet(nil, &info).String()
704         tr, err := cl.AddTorrent(&mi)
705         require.NoError(t, err)
706         require.True(t, tr.Seeding())
707         tr.VerifyData()
708         return magnet
709 }
710
711 // https://github.com/anacrolix/torrent/issues/114
712 func TestMultipleTorrentsWithEncryption(t *testing.T) {
713         testSeederLeecherPair(
714                 t,
715                 func(cfg *ClientConfig) {
716                         cfg.HeaderObfuscationPolicy.Preferred = true
717                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
718                 },
719                 func(cfg *ClientConfig) {
720                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
721                 },
722         )
723 }
724
725 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
726 // seeder config is done first.
727 func testSeederLeecherPair(t *testing.T, seeder, leecher func(*ClientConfig)) {
728         cfg := TestingConfig(t)
729         cfg.Seed = true
730         cfg.DataDir = filepath.Join(cfg.DataDir, "server")
731         os.Mkdir(cfg.DataDir, 0o755)
732         seeder(cfg)
733         server, err := NewClient(cfg)
734         require.NoError(t, err)
735         defer server.Close()
736         defer testutil.ExportStatusWriter(server, "s", t)()
737         magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
738         // Extra torrents are added to test the seeder having to match incoming obfuscated headers
739         // against more than one torrent. See issue #114
740         makeMagnet(t, server, cfg.DataDir, "test2")
741         for i := 0; i < 100; i++ {
742                 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
743         }
744         cfg = TestingConfig(t)
745         cfg.DataDir = filepath.Join(cfg.DataDir, "client")
746         leecher(cfg)
747         client, err := NewClient(cfg)
748         require.NoError(t, err)
749         defer client.Close()
750         defer testutil.ExportStatusWriter(client, "c", t)()
751         tr, err := client.AddMagnet(magnet1)
752         require.NoError(t, err)
753         tr.AddClientPeer(server)
754         <-tr.GotInfo()
755         tr.DownloadAll()
756         client.WaitAll()
757 }
758
759 // This appears to be the situation with the S3 BitTorrent client.
760 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
761         // Leecher prefers obfuscation, but the seeder does not allow it.
762         testSeederLeecherPair(
763                 t,
764                 func(cfg *ClientConfig) {
765                         cfg.HeaderObfuscationPolicy.Preferred = false
766                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
767                 },
768                 func(cfg *ClientConfig) {
769                         cfg.HeaderObfuscationPolicy.Preferred = true
770                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
771                 },
772         )
773 }
774
775 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
776         // Leecher prefers no obfuscation, but the seeder enforces it.
777         testSeederLeecherPair(
778                 t,
779                 func(cfg *ClientConfig) {
780                         cfg.HeaderObfuscationPolicy.Preferred = true
781                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
782                 },
783                 func(cfg *ClientConfig) {
784                         cfg.HeaderObfuscationPolicy.Preferred = false
785                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
786                 },
787         )
788 }
789
790 func TestClientAddressInUse(t *testing.T) {
791         s, _ := NewUtpSocket("udp", "localhost:50007", nil, log.Default)
792         if s != nil {
793                 defer s.Close()
794         }
795         cfg := TestingConfig(t).SetListenAddr("localhost:50007")
796         cfg.DisableUTP = false
797         cl, err := NewClient(cfg)
798         if err == nil {
799                 assert.Nil(t, cl.Close())
800         }
801         require.Error(t, err)
802         require.Nil(t, cl)
803 }
804
805 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
806         cc := TestingConfig(t)
807         cc.DisableUTP = true
808         cc.NoDHT = false
809         cl, err := NewClient(cc)
810         require.NoError(t, err)
811         defer cl.Close()
812         assert.NotEmpty(t, cl.DhtServers())
813 }
814
815 func TestClientDisabledImplicitNetworksButDhtEnabled(t *testing.T) {
816         cfg := TestingConfig(t)
817         cfg.DisableTCP = true
818         cfg.DisableUTP = true
819         cfg.NoDHT = false
820         cl, err := NewClient(cfg)
821         require.NoError(t, err)
822         defer cl.Close()
823         assert.Empty(t, cl.listeners)
824         assert.NotEmpty(t, cl.DhtServers())
825 }
826
827 func TestBadPeerIpPort(t *testing.T) {
828         for _, tc := range []struct {
829                 title      string
830                 ip         net.IP
831                 port       int
832                 expectedOk bool
833                 setup      func(*Client)
834         }{
835                 {"empty both", nil, 0, true, func(*Client) {}},
836                 {"empty/nil ip", nil, 6666, true, func(*Client) {}},
837                 {
838                         "empty port",
839                         net.ParseIP("127.0.0.1/32"),
840                         0, true,
841                         func(*Client) {},
842                 },
843                 {
844                         "in doppleganger addresses",
845                         net.ParseIP("127.0.0.1/32"),
846                         2322,
847                         true,
848                         func(cl *Client) {
849                                 cl.dopplegangerAddrs["10.0.0.1:2322"] = struct{}{}
850                         },
851                 },
852                 {
853                         "in IP block list",
854                         net.ParseIP("10.0.0.1"),
855                         2322,
856                         true,
857                         func(cl *Client) {
858                                 cl.ipBlockList = iplist.New([]iplist.Range{
859                                         {First: net.ParseIP("10.0.0.1"), Last: net.ParseIP("10.0.0.255")},
860                                 })
861                         },
862                 },
863                 {
864                         "in bad peer IPs",
865                         net.ParseIP("10.0.0.1"),
866                         2322,
867                         true,
868                         func(cl *Client) {
869                                 ipAddr, ok := netip.AddrFromSlice(net.ParseIP("10.0.0.1"))
870                                 require.True(t, ok)
871                                 cl.badPeerIPs = map[netip.Addr]struct{}{}
872                                 cl.badPeerIPs[ipAddr] = struct{}{}
873                         },
874                 },
875                 {
876                         "good",
877                         net.ParseIP("10.0.0.1"),
878                         2322,
879                         false,
880                         func(cl *Client) {},
881                 },
882         } {
883                 t.Run(tc.title, func(t *testing.T) {
884                         cfg := TestingConfig(t)
885                         cfg.DisableTCP = true
886                         cfg.DisableUTP = true
887                         cfg.NoDHT = false
888                         cl, err := NewClient(cfg)
889                         require.NoError(t, err)
890                         defer cl.Close()
891
892                         tc.setup(cl)
893                         require.Equal(t, tc.expectedOk, cl.badPeerIPPort(tc.ip, tc.port))
894                 })
895         }
896 }
897
898 // https://github.com/anacrolix/torrent/issues/837
899 func TestClientConfigSetHandlerNotIgnored(t *testing.T) {
900         cfg := TestingConfig(t)
901         cfg.Logger.SetHandlers(log.DiscardHandler)
902         c := qt.New(t)
903         cl, err := NewClient(cfg)
904         c.Assert(err, qt.IsNil)
905         defer cl.Close()
906         c.Assert(cl.logger.Handlers, qt.HasLen, 1)
907         h := cl.logger.Handlers[0].(log.StreamHandler)
908         c.Check(h.W, qt.Equals, io.Discard)
909 }