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