17 "github.com/anacrolix/dht"
18 "github.com/anacrolix/dht/krpc"
19 "github.com/anacrolix/log"
20 "github.com/anacrolix/missinggo"
21 "github.com/anacrolix/missinggo/pproffd"
22 "github.com/anacrolix/missinggo/pubsub"
23 "github.com/anacrolix/missinggo/slices"
24 "github.com/anacrolix/sync"
25 "github.com/davecgh/go-spew/spew"
26 "github.com/dustin/go-humanize"
27 "github.com/google/btree"
28 "golang.org/x/time/rate"
30 "github.com/anacrolix/torrent/bencode"
31 "github.com/anacrolix/torrent/iplist"
32 "github.com/anacrolix/torrent/metainfo"
33 "github.com/anacrolix/torrent/mse"
34 pp "github.com/anacrolix/torrent/peer_protocol"
35 "github.com/anacrolix/torrent/storage"
38 // Clients contain zero or more Torrents. A Client manages a blocklist, the
39 // TCP/UDP protocol ports, and DHT as desired.
43 closed missinggo.Event
50 defaultStorage *storage.Client
53 dhtServers []*dht.Server
54 ipBlockList iplist.Ranger
55 // Our BitTorrent protocol extension bytes, sent in our BT handshakes.
56 extensionBytes peerExtensionBytes
57 uploadLimit *rate.Limiter
58 downloadLimit *rate.Limiter
60 // Set of addresses that have our client ID. This intentionally will
61 // include ourselves if we end up trying to connect to our own address
62 // through legitimate channels.
63 dopplegangerAddrs map[string]struct{}
64 badPeerIPs map[string]struct{}
65 torrents map[metainfo.Hash]*Torrent
66 // An aggregate of stats over all connections.
70 func (cl *Client) BadPeerIPs() []string {
73 return cl.badPeerIPsLocked()
76 func (cl *Client) badPeerIPsLocked() []string {
77 return slices.FromMapKeys(cl.badPeerIPs).([]string)
80 func (cl *Client) PeerID() PeerID {
84 type torrentAddr string
86 func (torrentAddr) Network() string { return "" }
88 func (me torrentAddr) String() string { return string(me) }
90 func (cl *Client) LocalPort() (port int) {
91 cl.eachListener(func(l socket) bool {
92 _port := missinggo.AddrPort(l.Addr())
98 } else if port != _port {
99 panic("mismatched ports")
106 func writeDhtServerStatus(w io.Writer, s *dht.Server) {
107 dhtStats := s.Stats()
108 fmt.Fprintf(w, "\t# Nodes: %d (%d good, %d banned)\n", dhtStats.Nodes, dhtStats.GoodNodes, dhtStats.BadNodes)
109 fmt.Fprintf(w, "\tServer ID: %x\n", s.ID())
110 fmt.Fprintf(w, "\tAnnounces: %d\n", dhtStats.SuccessfulOutboundAnnouncePeerQueries)
111 fmt.Fprintf(w, "\tOutstanding transactions: %d\n", dhtStats.OutstandingTransactions)
114 // Writes out a human readable status of the client, such as for writing to a
116 func (cl *Client) WriteStatus(_w io.Writer) {
119 w := bufio.NewWriter(_w)
121 fmt.Fprintf(w, "Listen port: %d\n", cl.LocalPort())
122 fmt.Fprintf(w, "Peer ID: %+q\n", cl.PeerID())
123 fmt.Fprintf(w, "Announce key: %x\n", cl.announceKey())
124 fmt.Fprintf(w, "Banned IPs: %d\n", len(cl.badPeerIPsLocked()))
125 cl.eachDhtServer(func(s *dht.Server) {
126 fmt.Fprintf(w, "%s DHT server at %s:\n", s.Addr().Network(), s.Addr().String())
127 writeDhtServerStatus(w, s)
129 spew.Fdump(w, cl.stats)
130 fmt.Fprintf(w, "# Torrents: %d\n", len(cl.torrentsAsSlice()))
132 for _, t := range slices.Sort(cl.torrentsAsSlice(), func(l, r *Torrent) bool {
133 return l.InfoHash().AsString() < r.InfoHash().AsString()
136 fmt.Fprint(w, "<unknown name>")
138 fmt.Fprint(w, t.name())
142 fmt.Fprintf(w, "%f%% of %d bytes (%s)", 100*(1-float64(t.bytesMissingLocked())/float64(t.info.TotalLength())), t.length, humanize.Bytes(uint64(t.info.TotalLength())))
144 w.WriteString("<missing metainfo>")
152 const debugLogValue = "debug"
154 func (cl *Client) debugLogFilter(m *log.Msg) bool {
155 if !cl.config.Debug {
156 _, ok := m.Values()[debugLogValue]
162 func (cl *Client) initLogger() {
163 cl.logger = log.Default.Clone().AddValue(cl).AddFilter(log.NewFilter(cl.debugLogFilter))
166 func (cl *Client) announceKey() int32 {
167 return int32(binary.BigEndian.Uint32(cl.peerID[16:20]))
170 func NewClient(cfg *Config) (cl *Client, err error) {
182 halfOpenLimit: cfg.HalfOpenConnsPerTorrent,
184 dopplegangerAddrs: make(map[string]struct{}),
185 torrents: make(map[metainfo.Hash]*Torrent),
194 if cfg.UploadRateLimiter == nil {
195 cl.uploadLimit = unlimited
197 cl.uploadLimit = cfg.UploadRateLimiter
199 if cfg.DownloadRateLimiter == nil {
200 cl.downloadLimit = unlimited
202 cl.downloadLimit = cfg.DownloadRateLimiter
204 cl.extensionBytes = defaultPeerExtensionBytes()
206 storageImpl := cfg.DefaultStorage
207 if storageImpl == nil {
208 // We'd use mmap but HFS+ doesn't support sparse files.
209 storageImpl = storage.NewFile(cfg.DataDir)
210 cl.onClose = append(cl.onClose, func() {
211 if err := storageImpl.Close(); err != nil {
212 log.Printf("error closing default storage: %s", err)
216 cl.defaultStorage = storage.NewClient(storageImpl)
217 if cfg.IPBlocklist != nil {
218 cl.ipBlockList = cfg.IPBlocklist
221 if cfg.PeerID != "" {
222 missinggo.CopyExact(&cl.peerID, cfg.PeerID)
224 o := copy(cl.peerID[:], cfg.Bep20)
225 _, err = rand.Read(cl.peerID[o:])
227 panic("error generating peer id")
231 cl.conns, err = listenAll(cl.enabledPeerNetworks(), cl.config.ListenHost, cl.config.ListenPort, cl.config.ProxyURL)
238 for _, s := range cl.conns {
239 if peerNetworkEnabled(s.Addr().Network(), cl.config) {
240 go cl.acceptConnections(s)
246 for _, s := range cl.conns {
247 if pc, ok := s.(net.PacketConn); ok {
248 ds, err := cl.newDhtServer(pc)
252 cl.dhtServers = append(cl.dhtServers, ds)
260 func (cl *Client) enabledPeerNetworks() (ns []string) {
261 for _, n := range allPeerNetworks {
262 if peerNetworkEnabled(n, cl.config) {
269 func (cl *Client) newDhtServer(conn net.PacketConn) (s *dht.Server, err error) {
270 cfg := dht.ServerConfig{
271 IPBlocklist: cl.ipBlockList,
273 OnAnnouncePeer: cl.onDHTAnnouncePeer,
274 PublicIP: func() net.IP {
275 if connIsIpv6(conn) && cl.config.PublicIp6 != nil {
276 return cl.config.PublicIp6
278 return cl.config.PublicIp4
280 StartingNodes: cl.config.DhtStartingNodes,
282 s, err = dht.NewServer(&cfg)
285 if _, err := s.Bootstrap(); err != nil {
286 log.Printf("error bootstrapping dht: %s", err)
293 func firstNonEmptyString(ss ...string) string {
294 for _, s := range ss {
302 func (cl *Client) Closed() <-chan struct{} {
308 func (cl *Client) eachDhtServer(f func(*dht.Server)) {
309 for _, ds := range cl.dhtServers {
314 func (cl *Client) closeSockets() {
315 cl.eachListener(func(l socket) bool {
322 // Stops the client. All connections to peers are closed and all activity will
324 func (cl *Client) Close() {
328 cl.eachDhtServer(func(s *dht.Server) { s.Close() })
330 for _, t := range cl.torrents {
333 for _, f := range cl.onClose {
339 func (cl *Client) ipBlockRange(ip net.IP) (r iplist.Range, blocked bool) {
340 if cl.ipBlockList == nil {
343 return cl.ipBlockList.Lookup(ip)
346 func (cl *Client) ipIsBlocked(ip net.IP) bool {
347 _, blocked := cl.ipBlockRange(ip)
351 func (cl *Client) waitAccept() {
353 for _, t := range cl.torrents {
358 if cl.closed.IsSet() {
365 func (cl *Client) rejectAccepted(conn net.Conn) bool {
366 ra := conn.RemoteAddr()
367 rip := missinggo.AddrIP(ra)
368 if cl.config.DisableIPv4Peers && rip.To4() != nil {
371 if cl.config.DisableIPv4 && len(rip) == net.IPv4len {
374 if cl.config.DisableIPv6 && len(rip) == net.IPv6len && rip.To4() == nil {
377 return cl.badPeerIPPort(rip, missinggo.AddrPort(ra))
380 func (cl *Client) acceptConnections(l net.Listener) {
382 conn, err := l.Accept()
383 conn = pproffd.WrapNetConn(conn)
385 closed := cl.closed.IsSet()
388 reject = cl.rejectAccepted(conn)
399 // I think something harsher should happen here? Our accept
400 // routine just fucked off.
405 torrent.Add("rejected accepted connections", 1)
408 go cl.incomingConnection(conn)
410 log.Fmsg("accepted %s connection from %s", conn.RemoteAddr().Network(), conn.RemoteAddr()).AddValue(debugLogValue).Log(cl.logger)
411 torrent.Add(fmt.Sprintf("accepted conn remote IP len=%d", len(missinggo.AddrIP(conn.RemoteAddr()))), 1)
412 torrent.Add(fmt.Sprintf("accepted conn network=%s", conn.RemoteAddr().Network()), 1)
413 torrent.Add(fmt.Sprintf("accepted on %s listener", l.Addr().Network()), 1)
418 func (cl *Client) incomingConnection(nc net.Conn) {
420 if tc, ok := nc.(*net.TCPConn); ok {
423 c := cl.newConnection(nc, false)
424 c.Discovery = peerSourceIncoming
425 cl.runReceivedConn(c)
428 // Returns a handle to the given torrent, if it's present in the client.
429 func (cl *Client) Torrent(ih metainfo.Hash) (t *Torrent, ok bool) {
432 t, ok = cl.torrents[ih]
436 func (cl *Client) torrent(ih metainfo.Hash) *Torrent {
437 return cl.torrents[ih]
440 type dialResult struct {
444 func countDialResult(err error) {
446 successfulDials.Add(1)
448 unsuccessfulDials.Add(1)
452 func reducedDialTimeout(minDialTimeout, max time.Duration, halfOpenLimit int, pendingPeers int) (ret time.Duration) {
453 ret = max / time.Duration((pendingPeers+halfOpenLimit)/halfOpenLimit)
454 if ret < minDialTimeout {
460 // Returns whether an address is known to connect to a client with our own ID.
461 func (cl *Client) dopplegangerAddr(addr string) bool {
462 _, ok := cl.dopplegangerAddrs[addr]
466 func (cl *Client) dialTCP(ctx context.Context, addr string) (c net.Conn, err error) {
468 // Can't bind to the listen address, even though we intend to create an
469 // endpoint pair that is distinct. Oh well.
471 // LocalAddr: cl.tcpListener.Addr(),
473 c, err = d.DialContext(ctx, "tcp"+ipNetworkSuffix(!cl.config.DisableIPv4 && !cl.config.DisableIPv4Peers, !cl.config.DisableIPv6), addr)
476 c.(*net.TCPConn).SetLinger(0)
478 c = pproffd.WrapNetConn(c)
482 func ipNetworkSuffix(allowIpv4, allowIpv6 bool) string {
484 case allowIpv4 && allowIpv6:
486 case allowIpv4 && !allowIpv6:
488 case !allowIpv4 && allowIpv6:
491 panic("unhandled ip network combination")
495 func dialUTP(ctx context.Context, addr string, sock utpSocket) (c net.Conn, err error) {
496 return sock.DialContext(ctx, "", addr)
499 var allPeerNetworks = []string{"tcp4", "tcp6", "udp4", "udp6"}
501 func peerNetworkEnabled(network string, cfg Config) bool {
502 c := func(s string) bool {
503 return strings.Contains(network, s)
506 if c("udp") || c("utp") {
510 if cfg.DisableTCP && c("tcp") {
513 if cfg.DisableIPv6 && c("6") {
519 // Returns a connection over UTP or TCP, whichever is first to connect.
520 func (cl *Client) dialFirst(ctx context.Context, addr string) net.Conn {
521 ctx, cancel := context.WithCancel(ctx)
522 // As soon as we return one connection, cancel the others.
525 resCh := make(chan dialResult, left)
526 dial := func(f func(_ context.Context, addr string) (net.Conn, error)) {
529 c, err := f(ctx, addr)
531 resCh <- dialResult{c}
537 cl.eachListener(func(s socket) bool {
538 if peerNetworkEnabled(s.Addr().Network(), cl.config) {
545 // Wait for a successful connection.
546 for ; left > 0 && res.Conn == nil; left-- {
549 // There are still incompleted dials.
551 for ; left > 0; left-- {
552 conn := (<-resCh).Conn
559 go torrent.Add(fmt.Sprintf("network dialed first: %s", res.Conn.RemoteAddr().Network()), 1)
564 func (cl *Client) noLongerHalfOpen(t *Torrent, addr string) {
565 if _, ok := t.halfOpen[addr]; !ok {
566 panic("invariant broken")
568 delete(t.halfOpen, addr)
572 // Performs initiator handshakes and returns a connection. Returns nil
573 // *connection if no connection for valid reasons.
574 func (cl *Client) handshakesConnection(ctx context.Context, nc net.Conn, t *Torrent, encryptHeader bool) (c *connection, err error) {
575 c = cl.newConnection(nc, true)
576 c.headerEncrypted = encryptHeader
577 ctx, cancel := context.WithTimeout(ctx, cl.config.HandshakesTimeout)
579 dl, ok := ctx.Deadline()
583 err = nc.SetDeadline(dl)
587 ok, err = cl.initiateHandshakes(c, t)
594 // Returns nil connection and nil error if no connection could be established
595 // for valid reasons.
596 func (cl *Client) establishOutgoingConnEx(t *Torrent, addr string, ctx context.Context, obfuscatedHeader bool) (c *connection, err error) {
597 nc := cl.dialFirst(ctx, addr)
602 if c == nil || err != nil {
606 return cl.handshakesConnection(ctx, nc, t, obfuscatedHeader)
609 // Returns nil connection and nil error if no connection could be established
610 // for valid reasons.
611 func (cl *Client) establishOutgoingConn(t *Torrent, addr string) (c *connection, err error) {
612 ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
614 obfuscatedHeaderFirst := !cl.config.DisableEncryption && !cl.config.PreferNoEncryption
615 c, err = cl.establishOutgoingConnEx(t, addr, ctx, obfuscatedHeaderFirst)
620 torrent.Add("initiated conn with preferred header obfuscation", 1)
623 if cl.config.ForceEncryption {
624 // We should have just tried with an obfuscated header. A plaintext
625 // header can't result in an encrypted connection, so we're done.
626 if !obfuscatedHeaderFirst {
627 panic(cl.config.EncryptionPolicy)
631 // Try again with encryption if we didn't earlier, or without if we did.
632 c, err = cl.establishOutgoingConnEx(t, addr, ctx, !obfuscatedHeaderFirst)
634 torrent.Add("initiated conn with fallback header obfuscation", 1)
639 // Called to dial out and run a connection. The addr we're given is already
640 // considered half-open.
641 func (cl *Client) outgoingConnection(t *Torrent, addr string, ps peerSource) {
642 c, err := cl.establishOutgoingConn(t, addr)
645 // Don't release lock between here and addConnection, unless it's for
647 cl.noLongerHalfOpen(t, addr)
650 log.Printf("error establishing outgoing connection: %s", err)
659 cl.runHandshookConn(c, t)
662 // The port number for incoming peer connections. 0 if the client isn't
664 func (cl *Client) incomingPeerPort() int {
665 return cl.LocalPort()
668 func (cl *Client) initiateHandshakes(c *connection, t *Torrent) (ok bool, err error) {
669 if c.headerEncrypted {
671 rw, c.cryptoMethod, err = mse.InitiateHandshake(
678 func() mse.CryptoMethod {
680 case cl.config.ForceEncryption:
681 return mse.CryptoMethodRC4
682 case cl.config.DisableEncryption:
683 return mse.CryptoMethodPlaintext
685 return mse.AllSupportedCrypto
694 ih, ok, err := cl.connBTHandshake(c, &t.infoHash)
695 if ih != t.infoHash {
701 // Calls f with any secret keys.
702 func (cl *Client) forSkeys(f func([]byte) bool) {
705 for ih := range cl.torrents {
712 // Do encryption and bittorrent handshakes as receiver.
713 func (cl *Client) receiveHandshakes(c *connection) (t *Torrent, err error) {
715 rw, c.headerEncrypted, c.cryptoMethod, err = handleEncryption(c.rw(), cl.forSkeys, cl.config.EncryptionPolicy)
718 if err == mse.ErrNoSecretKeyMatch {
723 if cl.config.ForceEncryption && !c.headerEncrypted {
724 err = errors.New("connection not encrypted")
727 ih, ok, err := cl.connBTHandshake(c, nil)
729 err = fmt.Errorf("error during bt handshake: %s", err)
741 // Returns !ok if handshake failed for valid reasons.
742 func (cl *Client) connBTHandshake(c *connection, ih *metainfo.Hash) (ret metainfo.Hash, ok bool, err error) {
743 res, ok, err := handshake(c.rw(), ih, cl.peerID, cl.extensionBytes)
744 if err != nil || !ok {
748 c.PeerExtensionBytes = res.peerExtensionBytes
749 c.PeerID = res.PeerID
750 c.completedHandshake = time.Now()
754 func (cl *Client) runReceivedConn(c *connection) {
755 err := c.conn.SetDeadline(time.Now().Add(cl.config.HandshakesTimeout))
759 t, err := cl.receiveHandshakes(c)
762 log.Printf("error receiving handshakes: %s", err)
771 cl.runHandshookConn(c, t)
774 func (cl *Client) runHandshookConn(c *connection, t *Torrent) {
776 if c.PeerID == cl.peerID {
779 addr := c.conn.RemoteAddr().String()
780 cl.dopplegangerAddrs[addr] = struct{}{}
782 // Because the remote address is not necessarily the same as its
783 // client's torrent listen address, we won't record the remote address
784 // as a doppleganger. Instead, the initiator can record *us* as the
789 c.conn.SetWriteDeadline(time.Time{})
790 c.r = deadlineReader{c.conn, c.r}
791 completedHandshakeConnectionFlags.Add(c.connectionFlags(), 1)
792 if connIsIpv6(c.conn) {
793 torrent.Add("completed handshake over ipv6", 1)
795 if err := t.addConnection(c); err != nil {
796 log.Fmsg("error adding connection: %s", err).AddValues(c, debugLogValue).Log(t.logger)
799 defer t.dropConnection(c)
800 go c.writer(time.Minute)
801 cl.sendInitialMessages(c, t)
802 err := c.mainReadLoop()
803 if err != nil && cl.config.Debug {
804 log.Printf("error during connection main read loop: %s", err)
808 func (cl *Client) sendInitialMessages(conn *connection, torrent *Torrent) {
810 if conn.fastEnabled() {
811 if torrent.haveAllPieces() {
812 conn.Post(pp.Message{Type: pp.HaveAll})
813 conn.sentHaves.AddRange(0, conn.t.NumPieces())
815 } else if !torrent.haveAnyPieces() {
816 conn.Post(pp.Message{Type: pp.HaveNone})
817 conn.sentHaves.Clear()
823 if conn.PeerExtensionBytes.SupportsExtended() && cl.extensionBytes.SupportsExtended() {
824 conn.Post(pp.Message{
826 ExtendedID: pp.HandshakeExtendedID,
827 ExtendedPayload: func() []byte {
828 d := map[string]interface{}{
829 "m": func() (ret map[string]int) {
830 ret = make(map[string]int, 2)
831 ret["ut_metadata"] = metadataExtendedId
832 if !cl.config.DisablePEX {
833 ret["ut_pex"] = pexExtendedId
837 "v": cl.config.ExtendedHandshakeClientVersion,
838 // No upload queue is implemented yet.
841 if !cl.config.DisableEncryption {
844 if torrent.metadataSizeKnown() {
845 d["metadata_size"] = torrent.metadataSize()
847 if p := cl.incomingPeerPort(); p != 0 {
850 yourip, err := addrCompactIP(conn.remoteAddr())
852 log.Printf("error calculating yourip field value in extension handshake: %s", err)
856 // log.Printf("sending %v", d)
857 b, err := bencode.Marshal(d)
865 if conn.PeerExtensionBytes.SupportsDHT() && cl.extensionBytes.SupportsDHT() && cl.haveDhtServer() {
866 conn.Post(pp.Message{
873 func (cl *Client) dhtPort() (ret uint16) {
874 cl.eachDhtServer(func(s *dht.Server) {
875 ret = uint16(missinggo.AddrPort(s.Addr()))
880 func (cl *Client) haveDhtServer() (ret bool) {
881 cl.eachDhtServer(func(_ *dht.Server) {
887 // Process incoming ut_metadata message.
888 func (cl *Client) gotMetadataExtensionMsg(payload []byte, t *Torrent, c *connection) error {
890 err := bencode.Unmarshal(payload, &d)
891 if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok {
892 } else if err != nil {
893 return fmt.Errorf("error unmarshalling bencode: %s", err)
895 msgType, ok := d["msg_type"]
897 return errors.New("missing msg_type field")
901 case pp.DataMetadataExtensionMsgType:
902 if !c.requestedMetadataPiece(piece) {
903 return fmt.Errorf("got unexpected piece %d", piece)
905 c.metadataRequests[piece] = false
906 begin := len(payload) - metadataPieceSize(d["total_size"], piece)
907 if begin < 0 || begin >= len(payload) {
908 return fmt.Errorf("data has bad offset in payload: %d", begin)
910 t.saveMetadataPiece(piece, payload[begin:])
911 c.allStats(add(1, func(cs *ConnStats) *Count { return &cs.ChunksReadUseful }))
912 c.lastUsefulChunkReceived = time.Now()
913 return t.maybeCompleteMetadata()
914 case pp.RequestMetadataExtensionMsgType:
915 if !t.haveMetadataPiece(piece) {
916 c.Post(t.newMetadataExtensionMessage(c, pp.RejectMetadataExtensionMsgType, d["piece"], nil))
919 start := (1 << 14) * piece
920 c.Post(t.newMetadataExtensionMessage(c, pp.DataMetadataExtensionMsgType, piece, t.metadataBytes[start:start+t.metadataPieceSize(piece)]))
922 case pp.RejectMetadataExtensionMsgType:
925 return errors.New("unknown msg_type value")
929 func (cl *Client) badPeerIPPort(ip net.IP, port int) bool {
933 if cl.dopplegangerAddr(net.JoinHostPort(ip.String(), strconv.FormatInt(int64(port), 10))) {
936 if _, ok := cl.ipBlockRange(ip); ok {
939 if _, ok := cl.badPeerIPs[ip.String()]; ok {
945 // Return a Torrent ready for insertion into a Client.
946 func (cl *Client) newTorrent(ih metainfo.Hash, specStorage storage.ClientImpl) (t *Torrent) {
947 // use provided storage, if provided
948 storageClient := cl.defaultStorage
949 if specStorage != nil {
950 storageClient = storage.NewClient(specStorage)
956 peers: prioritizedPeers{
958 getPrio: func(p Peer) peerPriority {
959 return bep40PriorityIgnoreError(cl.publicAddr(p.IP), p.addr())
962 conns: make(map[*connection]struct{}, 2*cl.config.EstablishedConnsPerTorrent),
964 halfOpen: make(map[string]Peer),
965 pieceStateChanges: pubsub.NewPubSub(),
967 storageOpener: storageClient,
968 maxEstablishedConns: cl.config.EstablishedConnsPerTorrent,
970 networkingEnabled: true,
972 metadataChanged: sync.Cond{
976 t.logger = cl.logger.Clone().AddValue(t)
977 t.setChunkSize(defaultChunkSize)
981 // A file-like handle to some torrent data resource.
982 type Handle interface {
989 func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bool) {
990 return cl.AddTorrentInfoHashWithStorage(infoHash, nil)
993 // Adds a torrent by InfoHash with a custom Storage implementation.
994 // If the torrent already exists then this Storage is ignored and the
995 // existing torrent returned with `new` set to `false`
996 func (cl *Client) AddTorrentInfoHashWithStorage(infoHash metainfo.Hash, specStorage storage.ClientImpl) (t *Torrent, new bool) {
999 t, ok := cl.torrents[infoHash]
1004 t = cl.newTorrent(infoHash, specStorage)
1005 cl.eachDhtServer(func(s *dht.Server) {
1006 go t.dhtAnnouncer(s)
1008 cl.torrents[infoHash] = t
1009 t.updateWantPeersEvent()
1010 // Tickle Client.waitAccept, new torrent may want conns.
1011 cl.event.Broadcast()
1015 // Add or merge a torrent spec. If the torrent is already present, the
1016 // trackers will be merged with the existing ones. If the Info isn't yet
1017 // known, it will be set. The display name is replaced if the new spec
1018 // provides one. Returns new if the torrent wasn't already in the client.
1019 // Note that any `Storage` defined on the spec will be ignored if the
1020 // torrent is already present (i.e. `new` return value is `true`)
1021 func (cl *Client) AddTorrentSpec(spec *TorrentSpec) (t *Torrent, new bool, err error) {
1022 t, new = cl.AddTorrentInfoHashWithStorage(spec.InfoHash, spec.Storage)
1023 if spec.DisplayName != "" {
1024 t.SetDisplayName(spec.DisplayName)
1026 if spec.InfoBytes != nil {
1027 err = t.SetInfoBytes(spec.InfoBytes)
1033 defer cl.mu.Unlock()
1034 if spec.ChunkSize != 0 {
1035 t.setChunkSize(pp.Integer(spec.ChunkSize))
1037 t.addTrackers(spec.Trackers)
1042 func (cl *Client) dropTorrent(infoHash metainfo.Hash) (err error) {
1043 t, ok := cl.torrents[infoHash]
1045 err = fmt.Errorf("no such torrent")
1052 delete(cl.torrents, infoHash)
1056 func (cl *Client) allTorrentsCompleted() bool {
1057 for _, t := range cl.torrents {
1061 if !t.haveAllPieces() {
1068 // Returns true when all torrents are completely downloaded and false if the
1069 // client is stopped before that.
1070 func (cl *Client) WaitAll() bool {
1072 defer cl.mu.Unlock()
1073 for !cl.allTorrentsCompleted() {
1074 if cl.closed.IsSet() {
1082 // Returns handles to all the torrents loaded in the Client.
1083 func (cl *Client) Torrents() []*Torrent {
1085 defer cl.mu.Unlock()
1086 return cl.torrentsAsSlice()
1089 func (cl *Client) torrentsAsSlice() (ret []*Torrent) {
1090 for _, t := range cl.torrents {
1091 ret = append(ret, t)
1096 func (cl *Client) AddMagnet(uri string) (T *Torrent, err error) {
1097 spec, err := TorrentSpecFromMagnetURI(uri)
1101 T, _, err = cl.AddTorrentSpec(spec)
1105 func (cl *Client) AddTorrent(mi *metainfo.MetaInfo) (T *Torrent, err error) {
1106 T, _, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
1108 slices.MakeInto(&ss, mi.Nodes)
1113 func (cl *Client) AddTorrentFromFile(filename string) (T *Torrent, err error) {
1114 mi, err := metainfo.LoadFromFile(filename)
1118 return cl.AddTorrent(mi)
1121 func (cl *Client) DhtServers() []*dht.Server {
1122 return cl.dhtServers
1125 func (cl *Client) AddDHTNodes(nodes []string) {
1126 for _, n := range nodes {
1127 hmp := missinggo.SplitHostMaybePort(n)
1128 ip := net.ParseIP(hmp.Host)
1130 log.Printf("won't add DHT node with bad IP: %q", hmp.Host)
1133 ni := krpc.NodeInfo{
1134 Addr: krpc.NodeAddr{
1139 cl.eachDhtServer(func(s *dht.Server) {
1145 func (cl *Client) banPeerIP(ip net.IP) {
1146 if cl.badPeerIPs == nil {
1147 cl.badPeerIPs = make(map[string]struct{})
1149 cl.badPeerIPs[ip.String()] = struct{}{}
1152 func (cl *Client) newConnection(nc net.Conn, outgoing bool) (c *connection) {
1158 PeerMaxRequests: 250,
1159 writeBuffer: new(bytes.Buffer),
1161 c.writerCond.L = &cl.mu
1162 c.setRW(connStatsReadWriter{nc, c})
1163 c.r = &rateLimitedReader{
1164 l: cl.downloadLimit,
1170 func (cl *Client) onDHTAnnouncePeer(ih metainfo.Hash, p dht.Peer) {
1172 defer cl.mu.Unlock()
1180 Source: peerSourceDHTAnnouncePeer,
1184 func firstNotNil(ips ...net.IP) net.IP {
1185 for _, ip := range ips {
1193 func (cl *Client) eachListener(f func(socket) bool) {
1194 for _, s := range cl.conns {
1201 func (cl *Client) findListener(f func(net.Listener) bool) (ret net.Listener) {
1202 cl.eachListener(func(l socket) bool {
1209 func (cl *Client) publicIp(peer net.IP) net.IP {
1210 // TODO: Use BEP 10 to determine how peers are seeing us.
1211 if peer.To4() != nil {
1213 cl.config.PublicIp4,
1214 cl.findListenerIp(func(ip net.IP) bool { return ip.To4() != nil }),
1218 cl.config.PublicIp6,
1219 cl.findListenerIp(func(ip net.IP) bool { return ip.To4() == nil }),
1224 func (cl *Client) findListenerIp(f func(net.IP) bool) net.IP {
1225 return missinggo.AddrIP(cl.findListener(func(l net.Listener) bool {
1226 return f(missinggo.AddrIP(l.Addr()))
1230 // Our IP as a peer should see it.
1231 func (cl *Client) publicAddr(peer net.IP) ipPort {
1232 return ipPort{cl.publicIp(peer), uint16(cl.incomingPeerPort())}
1235 func (cl *Client) ListenAddrs() (ret []net.Addr) {
1236 cl.eachListener(func(l socket) bool {
1237 ret = append(ret, l.Addr())