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