]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Add BitTorrent v2 fields, consts and reference torrents
[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         c := qt.New(t)
343         ipl := iplist.New(nil)
344         require.NotNil(t, ipl)
345         cfg := TestingConfig(t)
346         cfg.IPBlocklist = ipl
347         cfg.NoDHT = false
348         cl, err := NewClient(cfg)
349         require.NoError(t, err)
350         defer cl.Close()
351         numServers := 0
352         cl.eachDhtServer(func(s DhtServer) {
353                 t.Log(s)
354                 assert.Equal(t, ipl, s.(AnacrolixDhtServerWrapper).Server.IPBlocklist())
355                 numServers++
356         })
357         c.Assert(numServers, qt.Not(qt.Equals), 0)
358 }
359
360 // Check that stuff is merged in subsequent AddTorrentSpec for the same
361 // infohash.
362 func TestAddTorrentSpecMerging(t *testing.T) {
363         cl, err := NewClient(TestingConfig(t))
364         require.NoError(t, err)
365         defer cl.Close()
366         dir, mi := testutil.GreetingTestTorrent()
367         defer os.RemoveAll(dir)
368         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
369                 InfoHash: mi.HashInfoBytes(),
370         })
371         require.NoError(t, err)
372         require.True(t, new)
373         require.Nil(t, tt.Info())
374         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
375         require.NoError(t, err)
376         require.False(t, new)
377         require.NotNil(t, tt.Info())
378 }
379
380 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
381         dir, mi := testutil.GreetingTestTorrent()
382         os.RemoveAll(dir)
383         cl, _ := NewClient(TestingConfig(t))
384         defer cl.Close()
385         tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
386                 InfoHash: mi.HashInfoBytes(),
387         })
388         tt.Drop()
389         assert.EqualValues(t, 0, len(cl.Torrents()))
390         select {
391         case <-tt.GotInfo():
392                 t.FailNow()
393         default:
394         }
395 }
396
397 func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
398         for i := 0; i < info.NumPieces(); i += 1 {
399                 p := info.Piece(i)
400                 ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
401         }
402 }
403
404 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
405         fileCacheDir := t.TempDir()
406         fileCache, err := filecache.NewCache(fileCacheDir)
407         require.NoError(t, err)
408         greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
409         defer os.RemoveAll(greetingDataTempDir)
410         filePieceStore := csf(fileCache)
411         info, err := greetingMetainfo.UnmarshalInfo()
412         require.NoError(t, err)
413         ih := greetingMetainfo.HashInfoBytes()
414         greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
415         require.NoError(t, err)
416         writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
417         // require.Equal(t, len(testutil.GreetingFileContents), written)
418         // require.NoError(t, err)
419         for i := 0; i < info.NumPieces(); i++ {
420                 p := info.Piece(i)
421                 if alreadyCompleted {
422                         require.NoError(t, greetingData.Piece(p).MarkComplete())
423                 }
424         }
425         cfg := TestingConfig(t)
426         // TODO: Disable network option?
427         cfg.DisableTCP = true
428         cfg.DisableUTP = true
429         cfg.DefaultStorage = filePieceStore
430         cl, err := NewClient(cfg)
431         require.NoError(t, err)
432         defer cl.Close()
433         tt, err := cl.AddTorrent(greetingMetainfo)
434         require.NoError(t, err)
435         psrs := tt.PieceStateRuns()
436         assert.Len(t, psrs, 1)
437         assert.EqualValues(t, 3, psrs[0].Length)
438         assert.Equal(t, alreadyCompleted, psrs[0].Complete)
439         if alreadyCompleted {
440                 r := tt.NewReader()
441                 quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil)
442         }
443 }
444
445 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
446         testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
447 }
448
449 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
450         testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
451 }
452
453 func TestAddMetainfoWithNodes(t *testing.T) {
454         cfg := TestingConfig(t)
455         cfg.ListenHost = func(string) string { return "" }
456         cfg.NoDHT = false
457         cfg.DhtStartingNodes = func(string) dht.StartingNodesGetter { return func() ([]dht.Addr, error) { return nil, nil } }
458         // For now, we want to just jam the nodes into the table, without verifying them first. Also the
459         // DHT code doesn't support mixing secure and insecure nodes if security is enabled (yet).
460         // cfg.DHTConfig.NoSecurity = true
461         cl, err := NewClient(cfg)
462         require.NoError(t, err)
463         defer cl.Close()
464         sum := func() (ret int64) {
465                 cl.eachDhtServer(func(s DhtServer) {
466                         ret += s.Stats().(dht.ServerStats).OutboundQueriesAttempted
467                 })
468                 return
469         }
470         assert.EqualValues(t, 0, sum())
471         tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
472         require.NoError(t, err)
473         // Nodes are not added or exposed in Torrent's metainfo. We just randomly
474         // check if the announce-list is here instead. TODO: Add nodes.
475         assert.Len(t, tt.metainfo.AnnounceList, 5)
476         // There are 6 nodes in the torrent file.
477         for sum() != int64(6*len(cl.dhtServers)) {
478                 time.Sleep(time.Millisecond)
479         }
480 }
481
482 type testDownloadCancelParams struct {
483         SetLeecherStorageCapacity bool
484         LeecherStorageCapacity    int64
485         Cancel                    bool
486 }
487
488 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
489         greetingTempDir, mi := testutil.GreetingTestTorrent()
490         defer os.RemoveAll(greetingTempDir)
491         cfg := TestingConfig(t)
492         cfg.Seed = true
493         cfg.DataDir = greetingTempDir
494         seeder, err := NewClient(cfg)
495         require.NoError(t, err)
496         defer seeder.Close()
497         defer testutil.ExportStatusWriter(seeder, "s", t)()
498         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
499         seederTorrent.VerifyData()
500         leecherDataDir := t.TempDir()
501         fc, err := filecache.NewCache(leecherDataDir)
502         require.NoError(t, err)
503         if ps.SetLeecherStorageCapacity {
504                 fc.SetCapacity(ps.LeecherStorageCapacity)
505         }
506         cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
507         cfg.DataDir = leecherDataDir
508         leecher, err := NewClient(cfg)
509         require.NoError(t, err)
510         defer leecher.Close()
511         defer testutil.ExportStatusWriter(leecher, "l", t)()
512         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
513                 ret = TorrentSpecFromMetaInfo(mi)
514                 ret.ChunkSize = 2
515                 return
516         }())
517         require.NoError(t, err)
518         assert.True(t, new)
519         psc := leecherGreeting.SubscribePieceStateChanges()
520         defer psc.Close()
521
522         leecherGreeting.cl.lock()
523         leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
524         if ps.Cancel {
525                 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces(), "")
526         }
527         leecherGreeting.cl.unlock()
528         done := make(chan struct{})
529         defer close(done)
530         go leecherGreeting.AddClientPeer(seeder)
531         completes := make(map[int]bool, 3)
532         expected := func() map[int]bool {
533                 if ps.Cancel {
534                         return map[int]bool{0: false, 1: false, 2: false}
535                 } else {
536                         return map[int]bool{0: true, 1: true, 2: true}
537                 }
538         }()
539         for !reflect.DeepEqual(completes, expected) {
540                 v := <-psc.Values
541                 completes[v.Index] = v.Complete
542         }
543 }
544
545 func TestTorrentDownloadAll(t *testing.T) {
546         testDownloadCancel(t, testDownloadCancelParams{})
547 }
548
549 func TestTorrentDownloadAllThenCancel(t *testing.T) {
550         testDownloadCancel(t, testDownloadCancelParams{
551                 Cancel: true,
552         })
553 }
554
555 // Ensure that it's an error for a peer to send an invalid have message.
556 func TestPeerInvalidHave(t *testing.T) {
557         cfg := TestingConfig(t)
558         cfg.DropMutuallyCompletePeers = false
559         cl, err := NewClient(cfg)
560         require.NoError(t, err)
561         defer cl.Close()
562         info := metainfo.Info{
563                 PieceLength: 1,
564                 Pieces:      make([]byte, 20),
565                 Files:       []metainfo.FileInfo{{Length: 1}},
566         }
567         infoBytes, err := bencode.Marshal(info)
568         require.NoError(t, err)
569         tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
570                 InfoBytes: infoBytes,
571                 InfoHash:  metainfo.HashBytes(infoBytes),
572                 Storage:   badStorage{},
573         })
574         require.NoError(t, err)
575         assert.True(t, _new)
576         defer tt.Drop()
577         cn := &PeerConn{Peer: Peer{
578                 t:         tt,
579                 callbacks: &cfg.Callbacks,
580         }}
581         tt.conns[cn] = struct{}{}
582         cn.peerImpl = cn
583         cl.lock()
584         defer cl.unlock()
585         assert.NoError(t, cn.peerSentHave(0))
586         assert.Error(t, cn.peerSentHave(1))
587 }
588
589 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
590         greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
591         defer os.RemoveAll(greetingTempDir)
592         cfg := TestingConfig(t)
593         cfg.DataDir = greetingTempDir
594         seeder, err := NewClient(TestingConfig(t))
595         require.NoError(t, err)
596         defer seeder.Close()
597         seeder.AddTorrentSpec(&TorrentSpec{
598                 InfoBytes: greetingMetainfo.InfoBytes,
599         })
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+2))
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         c := qt.New(t)
904         cl, err := NewClient(cfg)
905         c.Assert(err, qt.IsNil)
906         defer cl.Close()
907         c.Assert(cl.logger.Handlers, qt.HasLen, 1)
908         h := cl.logger.Handlers[0].(log.StreamHandler)
909         c.Check(h.W, qt.Equals, io.Discard)
910 }