]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Include count of peer conns in status
[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 // TestResponsive was the first test to fail if uTP is disabled and TCP sockets dial from the
258 // listening port.
259 func TestResponsiveTcpOnly(t *testing.T) {
260         seederDataDir, mi := testutil.GreetingTestTorrent()
261         defer os.RemoveAll(seederDataDir)
262         cfg := TestingConfig(t)
263         cfg.DisableUTP = true
264         cfg.Seed = true
265         cfg.DataDir = seederDataDir
266         seeder, err := NewClient(cfg)
267         require.Nil(t, err)
268         defer seeder.Close()
269         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
270         seederTorrent.VerifyData()
271         leecherDataDir := t.TempDir()
272         cfg = TestingConfig(t)
273         cfg.DataDir = leecherDataDir
274         leecher, err := NewClient(cfg)
275         require.Nil(t, err)
276         defer leecher.Close()
277         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
278                 ret = TorrentSpecFromMetaInfo(mi)
279                 ret.ChunkSize = 2
280                 return
281         }())
282         leecherTorrent.AddClientPeer(seeder)
283         reader := leecherTorrent.NewReader()
284         defer reader.Close()
285         reader.SetReadahead(0)
286         reader.SetResponsive()
287         b := make([]byte, 2)
288         _, err = reader.Seek(3, io.SeekStart)
289         require.NoError(t, err)
290         _, err = io.ReadFull(reader, b)
291         assert.Nil(t, err)
292         assert.EqualValues(t, "lo", string(b))
293         _, err = reader.Seek(11, io.SeekStart)
294         require.NoError(t, err)
295         n, err := io.ReadFull(reader, b)
296         assert.Nil(t, err)
297         assert.EqualValues(t, 2, n)
298         assert.EqualValues(t, "d\n", string(b))
299 }
300
301 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
302         seederDataDir, mi := testutil.GreetingTestTorrent()
303         defer os.RemoveAll(seederDataDir)
304         cfg := TestingConfig(t)
305         cfg.Seed = true
306         cfg.DataDir = seederDataDir
307         seeder, err := NewClient(cfg)
308         require.Nil(t, err)
309         defer seeder.Close()
310         seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
311         seederTorrent.VerifyData()
312         leecherDataDir := t.TempDir()
313         cfg = TestingConfig(t)
314         cfg.DataDir = leecherDataDir
315         leecher, err := NewClient(cfg)
316         require.Nil(t, err)
317         defer leecher.Close()
318         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
319                 ret = TorrentSpecFromMetaInfo(mi)
320                 ret.ChunkSize = 2
321                 return
322         }())
323         leecherTorrent.AddClientPeer(seeder)
324         reader := leecherTorrent.NewReader()
325         defer reader.Close()
326         reader.SetReadahead(0)
327         reader.SetResponsive()
328         b := make([]byte, 2)
329         _, err = reader.Seek(3, io.SeekStart)
330         require.NoError(t, err)
331         _, err = io.ReadFull(reader, b)
332         assert.Nil(t, err)
333         assert.EqualValues(t, "lo", string(b))
334         _, err = reader.Seek(11, io.SeekStart)
335         require.NoError(t, err)
336         leecherTorrent.Drop()
337         n, err := reader.Read(b)
338         assert.EqualError(t, err, "torrent closed")
339         assert.EqualValues(t, 0, n)
340 }
341
342 func TestDhtInheritBlocklist(t *testing.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         assert.EqualValues(t, 2, numServers)
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         seeder.AddTorrentSpec(&TorrentSpec{
597                 InfoBytes: greetingMetainfo.InfoBytes,
598         })
599 }
600
601 // Check that when the listen port is 0, all the protocols listened on have
602 // the same port, and it isn't zero.
603 func TestClientDynamicListenPortAllProtocols(t *testing.T) {
604         cl, err := NewClient(TestingConfig(t))
605         require.NoError(t, err)
606         defer cl.Close()
607         port := cl.LocalPort()
608         assert.NotEqual(t, 0, port)
609         cl.eachListener(func(s Listener) bool {
610                 assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
611                 return true
612         })
613 }
614
615 func TestClientDynamicListenTCPOnly(t *testing.T) {
616         cfg := TestingConfig(t)
617         cfg.DisableUTP = true
618         cfg.DisableTCP = false
619         cl, err := NewClient(cfg)
620         require.NoError(t, err)
621         defer cl.Close()
622         assert.NotEqual(t, 0, cl.LocalPort())
623 }
624
625 func TestClientDynamicListenUTPOnly(t *testing.T) {
626         cfg := TestingConfig(t)
627         cfg.DisableTCP = true
628         cfg.DisableUTP = false
629         cl, err := NewClient(cfg)
630         require.NoError(t, err)
631         defer cl.Close()
632         assert.NotEqual(t, 0, cl.LocalPort())
633 }
634
635 func totalConns(tts []*Torrent) (ret int) {
636         for _, tt := range tts {
637                 tt.cl.lock()
638                 ret += len(tt.conns)
639                 tt.cl.unlock()
640         }
641         return
642 }
643
644 func TestSetMaxEstablishedConn(t *testing.T) {
645         var tts []*Torrent
646         ih := testutil.GreetingMetaInfo().HashInfoBytes()
647         cfg := TestingConfig(t)
648         cfg.DisableAcceptRateLimiting = true
649         cfg.DropDuplicatePeerIds = true
650         for i := 0; i < 3; i += 1 {
651                 cl, err := NewClient(cfg)
652                 require.NoError(t, err)
653                 defer cl.Close()
654                 tt, _ := cl.AddTorrentInfoHash(ih)
655                 tt.SetMaxEstablishedConns(2)
656                 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)()
657                 tts = append(tts, tt)
658         }
659         addPeers := func() {
660                 for _, tt := range tts {
661                         for _, _tt := range tts {
662                                 // if tt != _tt {
663                                 tt.AddClientPeer(_tt.cl)
664                                 // }
665                         }
666                 }
667         }
668         waitTotalConns := func(num int) {
669                 for totalConns(tts) != num {
670                         addPeers()
671                         time.Sleep(time.Millisecond)
672                 }
673         }
674         addPeers()
675         waitTotalConns(6)
676         tts[0].SetMaxEstablishedConns(1)
677         waitTotalConns(4)
678         tts[0].SetMaxEstablishedConns(0)
679         waitTotalConns(2)
680         tts[0].SetMaxEstablishedConns(1)
681         addPeers()
682         waitTotalConns(4)
683         tts[0].SetMaxEstablishedConns(2)
684         addPeers()
685         waitTotalConns(6)
686 }
687
688 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
689 // client, and returns a magnet link.
690 func makeMagnet(t *testing.T, cl *Client, dir, name string) string {
691         os.MkdirAll(dir, 0o770)
692         file, err := os.Create(filepath.Join(dir, name))
693         require.NoError(t, err)
694         file.Write([]byte(name))
695         file.Close()
696         mi := metainfo.MetaInfo{}
697         mi.SetDefaults()
698         info := metainfo.Info{PieceLength: 256 * 1024}
699         err = info.BuildFromFilePath(filepath.Join(dir, name))
700         require.NoError(t, err)
701         mi.InfoBytes, err = bencode.Marshal(info)
702         require.NoError(t, err)
703         magnet := mi.Magnet(nil, &info).String()
704         tr, err := cl.AddTorrent(&mi)
705         require.NoError(t, err)
706         require.True(t, tr.Seeding())
707         tr.VerifyData()
708         return magnet
709 }
710
711 // https://github.com/anacrolix/torrent/issues/114
712 func TestMultipleTorrentsWithEncryption(t *testing.T) {
713         testSeederLeecherPair(
714                 t,
715                 func(cfg *ClientConfig) {
716                         cfg.HeaderObfuscationPolicy.Preferred = true
717                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
718                 },
719                 func(cfg *ClientConfig) {
720                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
721                 },
722         )
723 }
724
725 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
726 // seeder config is done first.
727 func testSeederLeecherPair(t *testing.T, seeder, leecher func(*ClientConfig)) {
728         cfg := TestingConfig(t)
729         cfg.Seed = true
730         cfg.DataDir = filepath.Join(cfg.DataDir, "server")
731         os.Mkdir(cfg.DataDir, 0o755)
732         seeder(cfg)
733         server, err := NewClient(cfg)
734         require.NoError(t, err)
735         defer server.Close()
736         defer testutil.ExportStatusWriter(server, "s", t)()
737         magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
738         // Extra torrents are added to test the seeder having to match incoming obfuscated headers
739         // against more than one torrent. See issue #114
740         makeMagnet(t, server, cfg.DataDir, "test2")
741         for i := 0; i < 100; i++ {
742                 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
743         }
744         cfg = TestingConfig(t)
745         cfg.DataDir = filepath.Join(cfg.DataDir, "client")
746         leecher(cfg)
747         client, err := NewClient(cfg)
748         require.NoError(t, err)
749         defer client.Close()
750         defer testutil.ExportStatusWriter(client, "c", t)()
751         tr, err := client.AddMagnet(magnet1)
752         require.NoError(t, err)
753         tr.AddClientPeer(server)
754         <-tr.GotInfo()
755         tr.DownloadAll()
756         client.WaitAll()
757 }
758
759 // This appears to be the situation with the S3 BitTorrent client.
760 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
761         // Leecher prefers obfuscation, but the seeder does not allow it.
762         testSeederLeecherPair(
763                 t,
764                 func(cfg *ClientConfig) {
765                         cfg.HeaderObfuscationPolicy.Preferred = false
766                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
767                 },
768                 func(cfg *ClientConfig) {
769                         cfg.HeaderObfuscationPolicy.Preferred = true
770                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
771                 },
772         )
773 }
774
775 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
776         // Leecher prefers no obfuscation, but the seeder enforces it.
777         testSeederLeecherPair(
778                 t,
779                 func(cfg *ClientConfig) {
780                         cfg.HeaderObfuscationPolicy.Preferred = true
781                         cfg.HeaderObfuscationPolicy.RequirePreferred = true
782                 },
783                 func(cfg *ClientConfig) {
784                         cfg.HeaderObfuscationPolicy.Preferred = false
785                         cfg.HeaderObfuscationPolicy.RequirePreferred = false
786                 },
787         )
788 }
789
790 func TestClientAddressInUse(t *testing.T) {
791         s, _ := NewUtpSocket("udp", ":50007", nil, log.Default)
792         if s != nil {
793                 defer s.Close()
794         }
795         cfg := TestingConfig(t).SetListenAddr(":50007")
796         cfg.DisableUTP = false
797         cl, err := NewClient(cfg)
798         require.Error(t, err)
799         require.Nil(t, cl)
800 }
801
802 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
803         cc := TestingConfig(t)
804         cc.DisableUTP = true
805         cc.NoDHT = false
806         cl, err := NewClient(cc)
807         require.NoError(t, err)
808         defer cl.Close()
809         assert.NotEmpty(t, cl.DhtServers())
810 }
811
812 func TestClientDisabledImplicitNetworksButDhtEnabled(t *testing.T) {
813         cfg := TestingConfig(t)
814         cfg.DisableTCP = true
815         cfg.DisableUTP = true
816         cfg.NoDHT = false
817         cl, err := NewClient(cfg)
818         require.NoError(t, err)
819         defer cl.Close()
820         assert.Empty(t, cl.listeners)
821         assert.NotEmpty(t, cl.DhtServers())
822 }
823
824 func TestBadPeerIpPort(t *testing.T) {
825         for _, tc := range []struct {
826                 title      string
827                 ip         net.IP
828                 port       int
829                 expectedOk bool
830                 setup      func(*Client)
831         }{
832                 {"empty both", nil, 0, true, func(*Client) {}},
833                 {"empty/nil ip", nil, 6666, true, func(*Client) {}},
834                 {
835                         "empty port",
836                         net.ParseIP("127.0.0.1/32"),
837                         0, true,
838                         func(*Client) {},
839                 },
840                 {
841                         "in doppleganger addresses",
842                         net.ParseIP("127.0.0.1/32"),
843                         2322,
844                         true,
845                         func(cl *Client) {
846                                 cl.dopplegangerAddrs["10.0.0.1:2322"] = struct{}{}
847                         },
848                 },
849                 {
850                         "in IP block list",
851                         net.ParseIP("10.0.0.1"),
852                         2322,
853                         true,
854                         func(cl *Client) {
855                                 cl.ipBlockList = iplist.New([]iplist.Range{
856                                         {First: net.ParseIP("10.0.0.1"), Last: net.ParseIP("10.0.0.255")},
857                                 })
858                         },
859                 },
860                 {
861                         "in bad peer IPs",
862                         net.ParseIP("10.0.0.1"),
863                         2322,
864                         true,
865                         func(cl *Client) {
866                                 ipAddr, ok := netip.AddrFromSlice(net.ParseIP("10.0.0.1"))
867                                 require.True(t, ok)
868                                 cl.badPeerIPs = map[netip.Addr]struct{}{}
869                                 cl.badPeerIPs[ipAddr] = struct{}{}
870                         },
871                 },
872                 {
873                         "good",
874                         net.ParseIP("10.0.0.1"),
875                         2322,
876                         false,
877                         func(cl *Client) {},
878                 },
879         } {
880                 t.Run(tc.title, func(t *testing.T) {
881                         cfg := TestingConfig(t)
882                         cfg.DisableTCP = true
883                         cfg.DisableUTP = true
884                         cfg.NoDHT = false
885                         cl, err := NewClient(cfg)
886                         require.NoError(t, err)
887                         defer cl.Close()
888
889                         tc.setup(cl)
890                         require.Equal(t, tc.expectedOk, cl.badPeerIPPort(tc.ip, tc.port))
891                 })
892         }
893 }