github.com/anacrolix/torrent@v1.61.0/client.go (about) 1 package torrent 2 3 import ( 4 "bufio" 5 "cmp" 6 "context" 7 "crypto/rand" 8 "encoding/binary" 9 "encoding/hex" 10 "errors" 11 "expvar" 12 "fmt" 13 "io" 14 "log/slog" 15 "math" 16 "net" 17 "net/http" 18 "net/netip" 19 "runtime" 20 "slices" 21 "strconv" 22 "time" 23 24 "github.com/RoaringBitmap/roaring" 25 "github.com/anacrolix/chansync" 26 "github.com/anacrolix/chansync/events" 27 "github.com/anacrolix/dht/v2" 28 "github.com/anacrolix/dht/v2/krpc" 29 . "github.com/anacrolix/generics" 30 g "github.com/anacrolix/generics" 31 "github.com/anacrolix/log" 32 "github.com/anacrolix/missinggo/v2" 33 "github.com/anacrolix/missinggo/v2/bitmap" 34 "github.com/anacrolix/missinggo/v2/panicif" 35 "github.com/anacrolix/missinggo/v2/pproffd" 36 "github.com/anacrolix/sync" 37 "github.com/anacrolix/torrent/internal/amortize" 38 "github.com/anacrolix/torrent/internal/extracmp" 39 "github.com/anacrolix/torrent/tracker" 40 "github.com/anacrolix/torrent/webtorrent" 41 "github.com/cespare/xxhash" 42 "github.com/dustin/go-humanize" 43 gbtree "github.com/google/btree" 44 "github.com/pion/webrtc/v4" 45 46 "github.com/anacrolix/torrent/bencode" 47 "github.com/anacrolix/torrent/internal/check" 48 "github.com/anacrolix/torrent/internal/limiter" 49 "github.com/anacrolix/torrent/iplist" 50 "github.com/anacrolix/torrent/metainfo" 51 "github.com/anacrolix/torrent/mse" 52 pp "github.com/anacrolix/torrent/peer_protocol" 53 "github.com/anacrolix/torrent/storage" 54 "github.com/anacrolix/torrent/types/infohash" 55 infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2" 56 ) 57 58 const webseedRequestUpdateTimerInterval = 5 * time.Second 59 60 // Clients contain zero or more Torrents. A Client manages a blocklist, the 61 // TCP/UDP protocol ports, and DHT as desired. 62 type Client struct { 63 // An aggregate of stats over all connections. First in struct to ensure 64-bit alignment of 64 // fields. See #262. 65 connStats AllConnStats 66 counters TorrentStatCounters 67 68 _mu lockWithDeferreds 69 unlockHandlers clientUnlockHandlers 70 check amortize.Value 71 // Used in constrained situations when the lock is held. 72 roaringIntIterator roaring.IntIterator 73 event sync.Cond 74 closed chansync.SetOnce 75 76 config *ClientConfig 77 logger log.Logger 78 slogger *slog.Logger 79 80 peerID PeerID 81 defaultStorage *storage.Client 82 onClose []func() 83 dialers []Dialer 84 listeners []Listener 85 dhtServers []DhtServer 86 ipBlockList iplist.Ranger 87 88 // Set of addresses that have our client ID. This intentionally will 89 // include ourselves if we end up trying to connect to our own address 90 // through legitimate channels. 91 dopplegangerAddrs map[string]struct{} 92 badPeerIPs map[netip.Addr]struct{} 93 // All Torrents once. 94 torrents map[*Torrent]struct{} 95 // All Torrents by their short infohashes (v1 if valid, and truncated v2 if valid). Unless the 96 // info has been obtained, there's no knowing if an infohash belongs to v1 or v2. TODO: Make 97 // this a weak pointer. 98 torrentsByShortHash map[InfoHash]*Torrent 99 100 // Piece request orderings grouped by storage. Value is value type because all fields are 101 // references. 102 pieceRequestOrder map[clientPieceRequestOrderKeySumType]clientPieceRequestOrderValue 103 104 acceptLimiter map[ipStr]int 105 numHalfOpen int 106 107 websocketTrackers websocketTrackers 108 regularTrackerAnnounceDispatcher regularTrackerAnnounceDispatcher 109 110 numWebSeedRequests map[webseedHostKeyHandle]int 111 112 activeAnnounceLimiter limiter.Instance 113 // TODO: Move this onto ClientConfig. 114 httpClient *http.Client 115 116 clientHolepunchAddrSets 117 118 defaultLocalLtepProtocolMap LocalLtepProtocolMap 119 120 upnpMappings []*upnpMapping 121 122 clientWebseedState 123 124 activePieceHashers int 125 } 126 127 type clientWebseedState struct { 128 webseedRequestTimer *time.Timer 129 webseedUpdateReason updateRequestReason 130 activeWebseedRequests map[webseedUniqueRequestKey]*webseedRequest 131 aprioriMap map[webseedUniqueRequestKey]aprioriMapValue 132 heapSlice []webseedRequestHeapElem 133 } 134 135 type ipStr string 136 137 func (cl *Client) BadPeerIPs() (ips []string) { 138 cl.rLock() 139 ips = cl.badPeerIPsLocked() 140 cl.rUnlock() 141 return 142 } 143 144 func (cl *Client) badPeerIPsLocked() (ips []string) { 145 ips = make([]string, len(cl.badPeerIPs)) 146 i := 0 147 for k := range cl.badPeerIPs { 148 ips[i] = k.String() 149 i += 1 150 } 151 return 152 } 153 154 func (cl *Client) PeerID() PeerID { 155 return cl.peerID 156 } 157 158 // Returns the port number for the first listener that has one. No longer assumes that all port 159 // numbers are the same, due to support for custom listeners. Returns zero if no port number is 160 // found. 161 func (cl *Client) LocalPort() (port int) { 162 for i := 0; i < len(cl.listeners); i += 1 { 163 if port = addrPortOrZero(cl.listeners[i].Addr()); port != 0 { 164 return 165 } 166 } 167 return 168 } 169 170 // Writes out a human-readable status of the client, such as for writing to an HTTP status page. 171 func (cl *Client) WriteStatus(_w io.Writer) { 172 cl.rLock() 173 defer cl.rUnlock() 174 w := bufio.NewWriter(_w) 175 defer w.Flush() 176 fmt.Fprintf(w, "Listen port: %d\n", cl.LocalPort()) 177 fmt.Fprintf(w, "Peer ID: %+q\n", cl.PeerID()) 178 fmt.Fprintf(w, "Extension bits: %v\n", cl.config.Extensions) 179 fmt.Fprintf(w, "Announce key: %x\n", cl.announceKey()) 180 fmt.Fprintf(w, "Banned IPs: %d\n", len(cl.badPeerIPsLocked())) 181 cl.eachDhtServer(func(s DhtServer) { 182 fmt.Fprintf(w, "%s DHT server at %s:\n", s.Addr().Network(), s.Addr().String()) 183 writeDhtServerStatus(w, s) 184 }) 185 dumpStats(w, cl.statsLocked()) 186 torrentsSlice := cl.torrentsAsSlice() 187 incomplete := 0 188 for _, t := range torrentsSlice { 189 if !t.Complete().Bool() { 190 incomplete++ 191 } 192 } 193 fmt.Fprintf(w, "# Torrents: %d (%v incomplete)\n", len(torrentsSlice), incomplete) 194 fmt.Fprintln(w) 195 slices.SortFunc(torrentsSlice, func(a, b *Torrent) int { 196 return cmp.Or( 197 extracmp.CompareBool(a.haveInfo(), b.haveInfo()), 198 func() int { 199 if a.haveInfo() && b.haveInfo() { 200 return -cmp.Compare(a.bytesLeft(), b.bytesLeft()) 201 } else { 202 return 0 203 } 204 }(), 205 cmp.Compare(a.canonicalShortInfohash().AsString(), b.canonicalShortInfohash().AsString()), 206 ) 207 }) 208 for _, t := range torrentsSlice { 209 if t.name() == "" { 210 fmt.Fprint(w, "<unknown name>") 211 } else { 212 fmt.Fprint(w, t.name()) 213 } 214 fmt.Fprint(w, "\n") 215 if t.info != nil { 216 fmt.Fprintf( 217 w, 218 "%f%% of %d bytes (%s)", 219 100*t.progressUnitFloat(), 220 t.length(), 221 humanize.Bytes(uint64(t.length()))) 222 } else { 223 w.WriteString("<missing metainfo>") 224 } 225 fmt.Fprint(w, "\n") 226 t.writeStatus(w) 227 fmt.Fprintln(w) 228 } 229 cl.writeRegularTrackerAnnouncerStatus(w) 230 } 231 232 func (cl *Client) writeRegularTrackerAnnouncerStatus(w io.Writer) { 233 cl.regularTrackerAnnounceDispatcher.writeStatus(w) 234 } 235 236 func (cl *Client) getLoggers() (log.Logger, *slog.Logger) { 237 logger := cl.config.Logger 238 slogger := cl.config.Slogger 239 // Maintain old behaviour if ClientConfig.Slogger isn't provided. Point Slogger to Logger so it 240 // appears unmodified. 241 if slogger == nil { 242 if logger.IsZero() { 243 logger = log.Default 244 } 245 if cl.config.Debug { 246 logger = logger.WithFilterLevel(log.Debug) 247 } 248 logger = logger.WithValues(cl) 249 return logger, logger.Slogger() 250 } 251 // Point logger to slogger. 252 if logger.IsZero() { 253 // I see "warning - 1" become info by the time it reaches an Erigon/geth logger. I think 254 // we've lost the old default of debug somewhere along the way. 255 logger = log.NewLogger().WithDefaultLevel(log.Debug) 256 logger.SetHandlers(log.SlogHandlerAsHandler{slogger.Handler()}) 257 } 258 // The unhandled case is that both logger and slogger are set. In this case, use them as normal. 259 return logger, slogger 260 } 261 262 func (cl *Client) initLogger() { 263 cl.logger, cl.slogger = cl.getLoggers() 264 } 265 266 func (cl *Client) announceKey() int32 { 267 return int32(binary.BigEndian.Uint32(cl.peerID[16:20])) 268 } 269 270 // Performs infallible parts of Client initialization. *Client and *ClientConfig must not be nil. 271 func (cl *Client) init(cfg *ClientConfig) { 272 cl.unlockHandlers.init() 273 cl.config = cfg 274 cl._mu.client = cl 275 cl.initLogger() 276 cl.regularTrackerAnnounceDispatcher.init(cl) 277 cfg.setRateLimiterBursts() 278 g.MakeMap(&cl.dopplegangerAddrs) 279 g.MakeMap(&cl.torrentsByShortHash) 280 g.MakeMap(&cl.torrents) 281 cl.torrentsByShortHash = make(map[metainfo.Hash]*Torrent) 282 cl.event.L = cl.locker() 283 cl.ipBlockList = cfg.IPBlocklist 284 cl.httpClient = &http.Client{ 285 Transport: cfg.WebTransport, 286 } 287 if cl.httpClient.Transport == nil { 288 cl.httpClient.Transport = &http.Transport{ 289 Proxy: cfg.HTTPProxy, 290 DialContext: cfg.HTTPDialContext, 291 // I think this value was observed from some webseeds. It seems reasonable to extend it 292 // to other uses of HTTP from the client. 293 MaxConnsPerHost: 10, 294 } 295 } 296 cfg.MetainfoSourcesClient = cmp.Or(cfg.MetainfoSourcesClient, cl.httpClient) 297 cl.defaultLocalLtepProtocolMap = makeBuiltinLtepProtocols(!cfg.DisablePEX) 298 g.MakeMap(&cl.numWebSeedRequests) 299 300 go cl.acceptLimitClearer() 301 //cl.logger.Levelf(log.Critical, "test after init") 302 303 storageImpl := cfg.DefaultStorage 304 if storageImpl == nil { 305 // We'd use mmap by default but HFS+ doesn't support sparse files. 306 storageImplCloser := storage.NewFile(cfg.DataDir) 307 cl.onClose = append(cl.onClose, func() { 308 if err := storageImplCloser.Close(); err != nil { 309 cl.logger.Printf("error closing default storage: %s", err) 310 } 311 }) 312 storageImpl = storageImplCloser 313 } 314 cl.defaultStorage = storage.NewClient(storageImpl) 315 316 if cfg.PeerID != "" { 317 missinggo.CopyExact(&cl.peerID, cfg.PeerID) 318 } else { 319 o := copy(cl.peerID[:], cfg.Bep20) 320 _, err := rand.Read(cl.peerID[o:]) 321 if err != nil { 322 panic("error generating peer id") 323 } 324 } 325 326 cl.websocketTrackers = websocketTrackers{ 327 PeerId: cl.peerID, 328 Logger: cl.logger.WithNames("websocketTrackers"), 329 GetAnnounceRequest: func( 330 event tracker.AnnounceEvent, infoHash [20]byte, 331 ) ( 332 tracker.AnnounceRequest, error, 333 ) { 334 cl.lock() 335 defer cl.unlock() 336 t, ok := cl.torrentsByShortHash[infoHash] 337 if !ok { 338 return tracker.AnnounceRequest{}, errors.New("torrent not tracked by client") 339 } 340 return t.announceRequest(event, infoHash), nil 341 }, 342 Proxy: cl.config.HTTPProxy, 343 WebsocketTrackerHttpHeader: cl.config.WebsocketTrackerHttpHeader, 344 ICEServers: cl.ICEServers(), 345 DialContext: cl.config.TrackerDialContext, 346 callbacks: &cl.config.Callbacks, 347 OnConn: func(dc webtorrent.DataChannelConn, dcc webtorrent.DataChannelContext) { 348 cl.lock() 349 defer cl.unlock() 350 t, ok := cl.torrentsByShortHash[dcc.InfoHash] 351 if !ok { 352 cl.logger.WithDefaultLevel(log.Warning).Printf( 353 "got webrtc conn for unloaded torrent with infohash %x", 354 dcc.InfoHash, 355 ) 356 dc.Close() 357 return 358 } 359 go t.onWebRtcConn(dc, dcc) 360 }, 361 } 362 363 cl.webseedRequestTimer = time.AfterFunc(webseedRequestUpdateTimerInterval, cl.updateWebseedRequestsTimerFunc) 364 } 365 366 // Creates a new Client. Takes ownership of the ClientConfig. Create another one if you want another 367 // Client. 368 func NewClient(cfg *ClientConfig) (cl *Client, err error) { 369 if cfg == nil { 370 cfg = NewDefaultClientConfig() 371 cfg.ListenPort = 0 372 } 373 cl = &Client{} 374 cl.init(cfg) 375 // Belongs after infallible init 376 defer func() { 377 if err != nil { 378 cl.Close() 379 cl = nil 380 } 381 }() 382 builtinListenNetworks := cl.listenNetworks() 383 sockets, err := listenAll( 384 builtinListenNetworks, 385 cl.config.ListenHost, 386 cl.config.ListenPort, 387 cl.firewallCallback, 388 cl.logger, 389 ) 390 if err != nil { 391 return 392 } 393 if len(sockets) == 0 && len(builtinListenNetworks) != 0 { 394 err = fmt.Errorf("no sockets created for networks %v", builtinListenNetworks) 395 return 396 } 397 398 // Check for panics. 399 cl.LocalPort() 400 401 for _, s := range sockets { 402 cl.onClose = append(cl.onClose, func() { go s.Close() }) 403 if peerNetworkEnabled(parseNetworkString(s.Addr().Network()), cl.config) { 404 if cl.config.DialForPeerConns { 405 cl.dialers = append(cl.dialers, s) 406 } 407 cl.listeners = append(cl.listeners, s) 408 if cl.config.AcceptPeerConnections { 409 go cl.acceptConnections(s) 410 } 411 } 412 } 413 414 if !cfg.NoDefaultPortForwarding { 415 go cl.forwardPort() 416 } 417 if !cfg.NoDHT { 418 for _, s := range sockets { 419 if pc, ok := s.(net.PacketConn); ok { 420 ds, err := cl.NewAnacrolixDhtServer(pc) 421 if err != nil { 422 panic(err) 423 } 424 cl.dhtServers = append(cl.dhtServers, AnacrolixDhtServerWrapper{ds}) 425 cl.onClose = append(cl.onClose, func() { ds.Close() }) 426 } 427 } 428 } 429 430 err = cl.checkConfig() 431 return 432 } 433 434 func (cl *Client) AddDhtServer(d DhtServer) { 435 cl.dhtServers = append(cl.dhtServers, d) 436 } 437 438 // Adds a Dialer for outgoing connections. All Dialers are used when attempting to connect to a 439 // given address for any Torrent. 440 func (cl *Client) AddDialer(d Dialer) { 441 cl.lock() 442 defer cl.unlock() 443 cl.dialers = append(cl.dialers, d) 444 for t := range cl.torrents { 445 t.openNewConns() 446 } 447 } 448 449 func (cl *Client) Listeners() []Listener { 450 return cl.listeners 451 } 452 453 // Registers a Listener, and starts Accepting on it. You must Close Listeners provided this way 454 // yourself. 455 func (cl *Client) AddListener(l Listener) { 456 cl.listeners = append(cl.listeners, l) 457 if cl.config.AcceptPeerConnections { 458 go cl.acceptConnections(l) 459 } 460 } 461 462 func (cl *Client) firewallCallback(net.Addr) bool { 463 cl.rLock() 464 block := !cl.wantConns() || !cl.config.AcceptPeerConnections 465 cl.rUnlock() 466 if block { 467 torrent.Add("connections firewalled", 1) 468 } else { 469 torrent.Add("connections not firewalled", 1) 470 } 471 return block 472 } 473 474 func (cl *Client) listenOnNetwork(n network) bool { 475 if n.Ipv4 && cl.config.DisableIPv4 { 476 return false 477 } 478 if n.Ipv6 && cl.config.DisableIPv6 { 479 return false 480 } 481 if n.Tcp && cl.config.DisableTCP { 482 return false 483 } 484 if n.Udp && cl.config.DisableUTP && cl.config.NoDHT { 485 return false 486 } 487 return true 488 } 489 490 func (cl *Client) listenNetworks() (ns []network) { 491 for _, n := range allPeerNetworks { 492 if cl.listenOnNetwork(n) { 493 ns = append(ns, n) 494 } 495 } 496 return 497 } 498 499 // Creates an anacrolix/dht Server, as would be done internally in NewClient, for the given conn. 500 func (cl *Client) NewAnacrolixDhtServer(conn net.PacketConn) (s *dht.Server, err error) { 501 logger := cl.logger.WithNames("dht", conn.LocalAddr().String()) 502 cfg := dht.ServerConfig{ 503 IPBlocklist: cl.ipBlockList, 504 Conn: conn, 505 OnAnnouncePeer: cl.onDHTAnnouncePeer, 506 PublicIP: func() net.IP { 507 if connIsIpv6(conn) && cl.config.PublicIp6 != nil { 508 return cl.config.PublicIp6 509 } 510 return cl.config.PublicIp4 511 }(), 512 StartingNodes: cl.config.DhtStartingNodes(conn.LocalAddr().Network()), 513 OnQuery: cl.config.DHTOnQuery, 514 Logger: logger, 515 } 516 if f := cl.config.ConfigureAnacrolixDhtServer; f != nil { 517 f(&cfg) 518 } 519 s, err = dht.NewServer(&cfg) 520 if err == nil { 521 go s.TableMaintainer() 522 } 523 return 524 } 525 526 func (cl *Client) Closed() events.Done { 527 return cl.closed.Done() 528 } 529 530 func (cl *Client) eachDhtServer(f func(DhtServer)) { 531 for _, ds := range cl.dhtServers { 532 f(ds) 533 } 534 } 535 536 // Stops the client. All connections to peers are closed and all activity will come to a halt. 537 func (cl *Client) Close() (errs []error) { 538 // Close atomically, allow systems to break early if we're contended on the Client lock. 539 cl.closed.Set() 540 cl.webseedRequestTimer.Stop() 541 var closeGroup sync.WaitGroup // For concurrent cleanup to complete before returning 542 cl.lock() 543 for t := range cl.torrents { 544 cl.dropTorrent(t, &closeGroup) 545 } 546 // Can we not modify cl.torrents as we delete from it? 547 panicif.NotZero(len(cl.torrents)) 548 panicif.NotZero(len(cl.torrentsByShortHash)) 549 cl.clearPortMappings() 550 for i := range cl.onClose { 551 cl.onClose[len(cl.onClose)-1-i]() 552 } 553 cl.unlock() 554 cl.event.Broadcast() 555 closeGroup.Wait() // defer is LIFO. We want to Wait() after cl.unlock() 556 return 557 } 558 559 func (cl *Client) ipBlockRange(ip net.IP) (r iplist.Range, blocked bool) { 560 if cl.ipBlockList == nil { 561 return 562 } 563 return cl.ipBlockList.Lookup(ip) 564 } 565 566 func (cl *Client) ipIsBlocked(ip net.IP) bool { 567 _, blocked := cl.ipBlockRange(ip) 568 return blocked 569 } 570 571 func (cl *Client) wantConns() bool { 572 if cl.config.AlwaysWantConns { 573 return true 574 } 575 for t := range cl.torrents { 576 if t.wantIncomingConns() { 577 return true 578 } 579 } 580 return false 581 } 582 583 // TODO: Apply filters for non-standard networks, particularly rate-limiting. 584 func (cl *Client) rejectAccepted(conn net.Conn) error { 585 if !cl.wantConns() { 586 return errors.New("don't want conns right now") 587 } 588 ra := conn.RemoteAddr() 589 if rip := addrIpOrNil(ra); rip != nil { 590 if cl.config.DisableIPv4Peers && rip.To4() != nil { 591 return errors.New("ipv4 peers disabled") 592 } 593 if cl.config.DisableIPv4 && len(rip) == net.IPv4len { 594 return errors.New("ipv4 disabled") 595 } 596 if cl.config.DisableIPv6 && len(rip) == net.IPv6len && rip.To4() == nil { 597 return errors.New("ipv6 disabled") 598 } 599 if cl.rateLimitAccept(rip) { 600 return errors.New("source IP accepted rate limited") 601 } 602 if cl.badPeerIPPort(rip, missinggo.AddrPort(ra)) { 603 return errors.New("bad source addr") 604 } 605 } 606 return nil 607 } 608 609 func (cl *Client) acceptConnections(l Listener) { 610 for { 611 conn, err := l.Accept() 612 torrent.Add("client listener accepts", 1) 613 if err == nil { 614 holepunchAddr, holepunchErr := addrPortFromPeerRemoteAddr(conn.RemoteAddr()) 615 if holepunchErr == nil { 616 cl.lock() 617 if g.MapContains(cl.undialableWithoutHolepunch, holepunchAddr) { 618 setAdd(&cl.accepted, holepunchAddr) 619 } 620 if g.MapContains( 621 cl.undialableWithoutHolepunchDialedAfterHolepunchConnect, 622 holepunchAddr, 623 ) { 624 setAdd(&cl.probablyOnlyConnectedDueToHolepunch, holepunchAddr) 625 } 626 cl.unlock() 627 } 628 } 629 conn = pproffd.WrapNetConn(conn) 630 cl.rLock() 631 closed := cl.closed.IsSet() 632 var reject error 633 if !closed && conn != nil { 634 reject = cl.rejectAccepted(conn) 635 } 636 cl.rUnlock() 637 if closed { 638 if conn != nil { 639 conn.Close() 640 } 641 return 642 } 643 if err != nil { 644 log.Fmsg("error accepting connection: %s", err).LogLevel(log.Debug, cl.logger) 645 continue 646 } 647 go func() { 648 if reject != nil { 649 torrent.Add("rejected accepted connections", 1) 650 cl.logger.LazyLog(log.Debug, func() log.Msg { 651 return log.Fmsg("rejecting accepted conn: %v", reject) 652 }) 653 conn.Close() 654 } else { 655 go cl.incomingConnection(conn) 656 } 657 cl.logger.LazyLog(log.Debug, func() log.Msg { 658 return log.Fmsg("accepted %q connection at %q from %q", 659 l.Addr().Network(), 660 conn.LocalAddr(), 661 conn.RemoteAddr(), 662 ) 663 }) 664 torrent.Add(fmt.Sprintf("accepted conn remote IP len=%d", len(addrIpOrNil(conn.RemoteAddr()))), 1) 665 torrent.Add(fmt.Sprintf("accepted conn network=%s", conn.RemoteAddr().Network()), 1) 666 torrent.Add(fmt.Sprintf("accepted on %s listener", l.Addr().Network()), 1) 667 }() 668 } 669 } 670 671 // Creates the PeerConn.connString for a regular net.Conn PeerConn. 672 func regularNetConnPeerConnConnString(nc net.Conn) string { 673 return fmt.Sprintf("%s-%s", nc.LocalAddr(), nc.RemoteAddr()) 674 } 675 676 func (cl *Client) incomingConnection(nc net.Conn) { 677 defer nc.Close() 678 if tc, ok := nc.(*net.TCPConn); ok { 679 tc.SetLinger(0) 680 } 681 remoteAddr, _ := tryIpPortFromNetAddr(nc.RemoteAddr()) 682 c := cl.newConnection( 683 nc, 684 newConnectionOpts{ 685 outgoing: false, 686 remoteAddr: nc.RemoteAddr(), 687 localPublicAddr: cl.publicAddr(remoteAddr.IP), 688 network: nc.RemoteAddr().Network(), 689 connString: regularNetConnPeerConnConnString(nc), 690 }) 691 c.Discovery = PeerSourceIncoming 692 cl.runReceivedConn(c) 693 694 cl.lock() 695 c.close() 696 cl.unlock() 697 } 698 699 // Returns a handle to the given torrent, if it's present in the client. 700 func (cl *Client) Torrent(ih metainfo.Hash) (t *Torrent, ok bool) { 701 cl.rLock() 702 defer cl.rUnlock() 703 t, ok = cl.torrentsByShortHash[ih] 704 return 705 } 706 707 type DialResult struct { 708 Conn net.Conn 709 Dialer Dialer 710 } 711 712 func countDialResult(err error) { 713 if err == nil { 714 torrent.Add("successful dials", 1) 715 } else { 716 torrent.Add("unsuccessful dials", 1) 717 } 718 } 719 720 func reducedDialTimeout(minDialTimeout, max time.Duration, halfOpenLimit, pendingPeers int) (ret time.Duration) { 721 ret = max / time.Duration((pendingPeers+halfOpenLimit)/halfOpenLimit) 722 if ret < minDialTimeout { 723 ret = minDialTimeout 724 } 725 return 726 } 727 728 // Returns whether an address is known to connect to a client with our own ID. 729 func (cl *Client) dopplegangerAddr(addr string) bool { 730 _, ok := cl.dopplegangerAddrs[addr] 731 return ok 732 } 733 734 // Returns a connection over UTP or TCP, whichever is first to connect. 735 func DialFirst(ctx context.Context, addr string, dialers []Dialer) (res DialResult) { 736 pool := dialPool{ 737 addr: addr, 738 } 739 defer pool.startDrainer() 740 for _, _s := range dialers { 741 pool.add(ctx, _s) 742 } 743 return pool.getFirst() 744 } 745 746 func dialFromSocket(ctx context.Context, s Dialer, addr string) net.Conn { 747 c, err := s.Dial(ctx, addr) 748 if err != nil { 749 log.ContextLogger(ctx).Levelf(log.Debug, "error dialing %q: %v", addr, err) 750 } 751 // This is a bit optimistic, but it looks non-trivial to thread this through the proxy code. Set 752 // it now in case we close the connection forthwith. Note this is also done in the TCP dialer 753 // code to increase the chance it's done. 754 if tc, ok := c.(*net.TCPConn); ok { 755 tc.SetLinger(0) 756 } 757 countDialResult(err) 758 return c 759 } 760 761 func (cl *Client) noLongerHalfOpen(t *Torrent, addr string, attemptKey outgoingConnAttemptKey) { 762 path := t.getHalfOpenPath(addr, attemptKey) 763 if !path.Exists() { 764 panic("should exist") 765 } 766 path.Delete() 767 cl.numHalfOpen-- 768 if cl.numHalfOpen < 0 { 769 panic("should not be possible") 770 } 771 for t := range cl.torrents { 772 t.openNewConns() 773 } 774 } 775 776 // Performs initiator handshakes and returns a connection. Returns nil *PeerConn if no connection 777 // for valid reasons. 778 func (cl *Client) initiateProtocolHandshakes( 779 ctx context.Context, 780 nc net.Conn, 781 t *Torrent, 782 encryptHeader bool, 783 newConnOpts newConnectionOpts, 784 ) ( 785 c *PeerConn, err error, 786 ) { 787 c = cl.newConnection(nc, newConnOpts) 788 c.headerEncrypted = encryptHeader 789 ctx, cancel := context.WithTimeout(ctx, cl.config.HandshakesTimeout) 790 defer cancel() 791 dl, ok := ctx.Deadline() 792 if !ok { 793 panic(ctx) 794 } 795 err = nc.SetDeadline(dl) 796 if err != nil { 797 panic(err) 798 } 799 err = cl.initiateHandshakes(ctx, c, t) 800 return 801 } 802 803 func doProtocolHandshakeOnDialResult( 804 t *Torrent, 805 obfuscatedHeader bool, 806 addr PeerRemoteAddr, 807 dr DialResult, 808 ) ( 809 c *PeerConn, err error, 810 ) { 811 cl := t.cl 812 nc := dr.Conn 813 addrIpPort, _ := tryIpPortFromNetAddr(addr) 814 815 c, err = cl.initiateProtocolHandshakes( 816 context.Background(), nc, t, obfuscatedHeader, 817 newConnectionOpts{ 818 outgoing: true, 819 remoteAddr: addr, 820 // It would be possible to retrieve a public IP from the dialer used here? 821 localPublicAddr: cl.publicAddr(addrIpPort.IP), 822 network: dr.Dialer.DialerNetwork(), 823 connString: regularNetConnPeerConnConnString(nc), 824 }) 825 if err != nil { 826 nc.Close() 827 } 828 return c, err 829 } 830 831 // Returns nil connection and nil error if no connection could be established for valid reasons. 832 func (cl *Client) dialAndCompleteHandshake(opts outgoingConnOpts) (c *PeerConn, err error) { 833 // It would be better if dial rate limiting could be tested when considering to open connections 834 // instead. Doing it here means if the limit is low, and the half-open limit is high, we could 835 // end up with lots of outgoing connection attempts pending that were initiated on stale data. 836 { 837 dialReservation := cl.config.DialRateLimiter.Reserve() 838 if !opts.receivedHolepunchConnect { 839 if !dialReservation.OK() { 840 err = errors.New("can't make dial limit reservation") 841 return 842 } 843 time.Sleep(dialReservation.Delay()) 844 } 845 } 846 torrent.Add("establish outgoing connection", 1) 847 addr := opts.peerInfo.Addr 848 dialPool := dialPool{ 849 resCh: make(chan DialResult), 850 addr: addr.String(), 851 } 852 defer dialPool.startDrainer() 853 dialTimeout := opts.t.getDialTimeoutUnlocked() 854 { 855 ctx, cancel := context.WithTimeout(context.Background(), dialTimeout) 856 defer cancel() 857 for _, d := range cl.dialers { 858 dialPool.add(ctx, d) 859 } 860 } 861 holepunchAddr, holepunchAddrErr := addrPortFromPeerRemoteAddr(addr) 862 headerObfuscationPolicy := opts.HeaderObfuscationPolicy 863 obfuscatedHeaderFirst := headerObfuscationPolicy.Preferred 864 firstDialResult := dialPool.getFirst() 865 if firstDialResult.Conn == nil { 866 // No dialers worked. Try to initiate a holepunching rendezvous. 867 if holepunchAddrErr == nil { 868 cl.lock() 869 if !opts.receivedHolepunchConnect { 870 g.MakeMapIfNilAndSet(&cl.undialableWithoutHolepunch, holepunchAddr, struct{}{}) 871 } 872 if !opts.skipHolepunchRendezvous { 873 opts.t.trySendHolepunchRendezvous(holepunchAddr) 874 } 875 cl.unlock() 876 } 877 err = fmt.Errorf("all initial dials failed") 878 return 879 } 880 if opts.receivedHolepunchConnect && holepunchAddrErr == nil { 881 cl.lock() 882 if g.MapContains(cl.undialableWithoutHolepunch, holepunchAddr) { 883 g.MakeMapIfNilAndSet(&cl.dialableOnlyAfterHolepunch, holepunchAddr, struct{}{}) 884 } 885 g.MakeMapIfNil(&cl.dialedSuccessfullyAfterHolepunchConnect) 886 g.MapInsert(cl.dialedSuccessfullyAfterHolepunchConnect, holepunchAddr, struct{}{}) 887 cl.unlock() 888 } 889 c, err = doProtocolHandshakeOnDialResult( 890 opts.t, 891 obfuscatedHeaderFirst, 892 addr, 893 firstDialResult, 894 ) 895 if err == nil { 896 torrent.Add("initiated conn with preferred header obfuscation", 1) 897 return 898 } 899 c.logger.Levelf( 900 log.Debug, 901 "error doing protocol handshake with header obfuscation %v", 902 obfuscatedHeaderFirst, 903 ) 904 firstDialResult.Conn.Close() 905 // We should have just tried with the preferred header obfuscation. If it was required, there's nothing else to try. 906 if headerObfuscationPolicy.RequirePreferred { 907 return 908 } 909 // Reuse the dialer that returned already but failed to handshake. 910 { 911 ctx, cancel := context.WithTimeout(context.Background(), dialTimeout) 912 defer cancel() 913 dialPool.add(ctx, firstDialResult.Dialer) 914 } 915 secondDialResult := dialPool.getFirst() 916 if secondDialResult.Conn == nil { 917 return 918 } 919 c, err = doProtocolHandshakeOnDialResult( 920 opts.t, 921 !obfuscatedHeaderFirst, 922 addr, 923 secondDialResult, 924 ) 925 if err == nil { 926 torrent.Add("initiated conn with fallback header obfuscation", 1) 927 return 928 } 929 c.logger.Levelf( 930 log.Debug, 931 "error doing protocol handshake with header obfuscation %v", 932 !obfuscatedHeaderFirst, 933 ) 934 secondDialResult.Conn.Close() 935 return 936 } 937 938 type outgoingConnOpts struct { 939 peerInfo PeerInfo 940 t *Torrent 941 // Don't attempt to connect unless a connect message is received after initiating a rendezvous. 942 requireRendezvous bool 943 // Don't send rendezvous requests to eligible relays. 944 skipHolepunchRendezvous bool 945 // Outgoing connection attempt is in response to holepunch connect message. 946 receivedHolepunchConnect bool 947 HeaderObfuscationPolicy HeaderObfuscationPolicy 948 } 949 950 // Called to dial out and run a connection. The addr we're given is already 951 // considered half-open. 952 func (cl *Client) outgoingConnection( 953 opts outgoingConnOpts, 954 attemptKey outgoingConnAttemptKey, 955 ) { 956 c, err := cl.dialAndCompleteHandshake(opts) 957 if err == nil { 958 c.conn.SetWriteDeadline(time.Time{}) 959 } 960 cl.lock() 961 defer cl.unlock() 962 // Don't release lock between here and addPeerConn, unless it's for failure. 963 cl.noLongerHalfOpen(opts.t, opts.peerInfo.Addr.String(), attemptKey) 964 if err != nil { 965 if cl.config.Debug { 966 cl.logger.Levelf( 967 log.Debug, 968 "error establishing outgoing connection to %v: %v", 969 opts.peerInfo.Addr, 970 err, 971 ) 972 } 973 return 974 } 975 defer c.close() 976 c.Discovery = opts.peerInfo.Source 977 c.trusted = opts.peerInfo.Trusted 978 opts.t.runHandshookConnLoggingErr(c) 979 } 980 981 // The port number for incoming peer connections. 0 if the client isn't listening. 982 func (cl *Client) incomingPeerPort() int { 983 return cl.LocalPort() 984 } 985 986 func (cl *Client) initiateHandshakes(ctx context.Context, c *PeerConn, t *Torrent) (err error) { 987 if c.headerEncrypted { 988 var rw io.ReadWriter 989 rw, c.cryptoMethod, err = mse.InitiateHandshakeContext( 990 ctx, 991 struct { 992 io.Reader 993 io.Writer 994 }{c.r, c.w}, 995 t.canonicalShortInfohash().Bytes(), 996 nil, 997 cl.config.CryptoProvides, 998 ) 999 c.setRW(rw) 1000 if err != nil { 1001 return fmt.Errorf("header obfuscation handshake: %w", err) 1002 } 1003 } 1004 localReservedBits := cl.config.Extensions 1005 handshakeIh := *t.canonicalShortInfohash() 1006 // If we're sending the v1 infohash, and we know the v2 infohash, set the v2 upgrade bit. This 1007 // means the peer can send the v2 infohash in the handshake to upgrade the connection. 1008 localReservedBits.SetBit(pp.ExtensionBitV2Upgrade, g.Some(handshakeIh) == t.infoHash && t.infoHashV2.Ok) 1009 ih, err := cl.connBtHandshake(context.TODO(), c, &handshakeIh, localReservedBits) 1010 if err != nil { 1011 return fmt.Errorf("bittorrent protocol handshake: %w", err) 1012 } 1013 if g.Some(ih) == t.infoHash { 1014 return nil 1015 } 1016 if t.infoHashV2.Ok && *t.infoHashV2.Value.ToShort() == ih { 1017 torrent.Add("initiated handshakes upgraded to v2", 1) 1018 c.v2 = true 1019 return nil 1020 } 1021 err = errors.New("bittorrent protocol handshake: peer infohash didn't match") 1022 return 1023 } 1024 1025 // Calls f with any secret keys. Note that it takes the Client lock, and so must be used from code 1026 // that won't also try to take the lock. This saves us copying all the infohashes everytime. 1027 func (cl *Client) forSkeys(f func([]byte) bool) { 1028 cl.rLock() 1029 defer cl.rUnlock() 1030 if false { // Emulate the bug from #114 1031 var firstIh InfoHash 1032 for ih := range cl.torrentsByShortHash { 1033 firstIh = ih 1034 break 1035 } 1036 for range cl.torrentsByShortHash { 1037 if !f(firstIh[:]) { 1038 break 1039 } 1040 } 1041 return 1042 } 1043 for ih := range cl.torrentsByShortHash { 1044 if !f(ih[:]) { 1045 break 1046 } 1047 } 1048 } 1049 1050 func (cl *Client) handshakeReceiverSecretKeys() mse.SecretKeyIter { 1051 if ret := cl.config.Callbacks.ReceiveEncryptedHandshakeSkeys; ret != nil { 1052 return ret 1053 } 1054 return cl.forSkeys 1055 } 1056 1057 // Do encryption and bittorrent handshakes as receiver. 1058 func (cl *Client) receiveHandshakes(c *PeerConn) (t *Torrent, err error) { 1059 var rw io.ReadWriter 1060 rw, c.headerEncrypted, c.cryptoMethod, err = handleEncryption( 1061 c.rw(), 1062 cl.handshakeReceiverSecretKeys(), 1063 cl.config.HeaderObfuscationPolicy, 1064 cl.config.CryptoSelector, 1065 ) 1066 c.setRW(rw) 1067 if err == nil || err == mse.ErrNoSecretKeyMatch { 1068 if c.headerEncrypted { 1069 torrent.Add("handshakes received encrypted", 1) 1070 } else { 1071 torrent.Add("handshakes received unencrypted", 1) 1072 } 1073 } else { 1074 torrent.Add("handshakes received with error while handling encryption", 1) 1075 } 1076 if err != nil { 1077 if err == mse.ErrNoSecretKeyMatch { 1078 err = nil 1079 } 1080 return 1081 } 1082 if cl.config.HeaderObfuscationPolicy.RequirePreferred && c.headerEncrypted != cl.config.HeaderObfuscationPolicy.Preferred { 1083 err = errors.New("connection does not have required header obfuscation") 1084 return 1085 } 1086 ih, err := cl.connBtHandshake(context.TODO(), c, nil, cl.config.Extensions) 1087 if err != nil { 1088 return nil, fmt.Errorf("during bt handshake: %w", err) 1089 } 1090 1091 cl.lock() 1092 t = cl.torrentsByShortHash[ih] 1093 if t != nil && t.infoHashV2.Ok && *t.infoHashV2.Value.ToShort() == ih { 1094 torrent.Add("v2 handshakes received", 1) 1095 c.v2 = true 1096 } 1097 cl.unlock() 1098 1099 return 1100 } 1101 1102 var successfulPeerWireProtocolHandshakePeerReservedBytes expvar.Map 1103 1104 func init() { 1105 torrent.Set( 1106 "successful_peer_wire_protocol_handshake_peer_reserved_bytes", 1107 &successfulPeerWireProtocolHandshakePeerReservedBytes) 1108 } 1109 1110 func (cl *Client) connBtHandshake(ctx context.Context, c *PeerConn, ih *metainfo.Hash, reservedBits PeerExtensionBits) (ret metainfo.Hash, err error) { 1111 res, err := pp.Handshake(ctx, c.rw(), ih, cl.peerID, reservedBits) 1112 if err != nil { 1113 return 1114 } 1115 successfulPeerWireProtocolHandshakePeerReservedBytes.Add( 1116 hex.EncodeToString(res.PeerExtensionBits[:]), 1) 1117 ret = res.Hash 1118 c.PeerExtensionBytes = res.PeerExtensionBits 1119 c.PeerID = res.PeerID 1120 c.completedHandshake = time.Now() 1121 if cb := cl.config.Callbacks.CompletedHandshake; cb != nil { 1122 cb(c, res.Hash) 1123 } 1124 return 1125 } 1126 1127 func (cl *Client) runReceivedConn(c *PeerConn) { 1128 err := c.conn.SetDeadline(time.Now().Add(cl.config.HandshakesTimeout)) 1129 if err != nil { 1130 panic(err) 1131 } 1132 t, err := cl.receiveHandshakes(c) 1133 if err != nil { 1134 cl.logger.LazyLog(log.Debug, func() log.Msg { 1135 return log.Fmsg( 1136 "error receiving handshakes on %v: %s", c, err, 1137 ).Add( 1138 "network", c.Network, 1139 ) 1140 }) 1141 torrent.Add("error receiving handshake", 1) 1142 cl.lock() 1143 cl.onBadAccept(c.RemoteAddr) 1144 cl.unlock() 1145 return 1146 } 1147 if t == nil { 1148 torrent.Add("received handshake for unloaded torrent", 1) 1149 cl.logger.LazyLog(log.Debug, func() log.Msg { 1150 return log.Fmsg("received handshake for unloaded torrent") 1151 }) 1152 cl.lock() 1153 cl.onBadAccept(c.RemoteAddr) 1154 cl.unlock() 1155 return 1156 } 1157 torrent.Add("received handshake for loaded torrent", 1) 1158 c.conn.SetWriteDeadline(time.Time{}) 1159 cl.lock() 1160 defer cl.unlock() 1161 t.runHandshookConnLoggingErr(c) 1162 } 1163 1164 // Client lock must be held before entering this. 1165 func (t *Torrent) runHandshookConn(pc *PeerConn) error { 1166 pc.setTorrent(t) 1167 cl := t.cl 1168 for i, b := range cl.config.MinPeerExtensions { 1169 if pc.PeerExtensionBytes[i]&b != b { 1170 return fmt.Errorf("peer did not meet minimum peer extensions: %x", pc.PeerExtensionBytes[:]) 1171 } 1172 } 1173 if pc.PeerID == cl.peerID { 1174 if pc.outgoing { 1175 connsToSelf.Add(1) 1176 addr := pc.RemoteAddr.String() 1177 cl.dopplegangerAddrs[addr] = struct{}{} 1178 } /* else { 1179 // Because the remote address is not necessarily the same as its client's torrent listen 1180 // address, we won't record the remote address as a doppleganger. Instead, the initiator 1181 // can record *us* as the doppleganger. 1182 } */ 1183 t.logger.Levelf(log.Debug, "local and remote peer ids are the same") 1184 return nil 1185 } 1186 pc.r = deadlineReader{pc.conn, pc.r} 1187 completedHandshakeConnectionFlags.Add(pc.connectionFlags(), 1) 1188 if connIsIpv6(pc.conn) { 1189 torrent.Add("completed handshake over ipv6", 1) 1190 } 1191 if err := t.addPeerConn(pc); err != nil { 1192 return fmt.Errorf("adding connection: %w", err) 1193 } 1194 defer t.dropConnection(pc) 1195 pc.addBuiltinLtepProtocols(!cl.config.DisablePEX) 1196 for _, cb := range pc.callbacks.PeerConnAdded { 1197 cb(pc) 1198 } 1199 pc.startMessageWriter() 1200 pc.sendInitialMessages() 1201 pc.initUpdateRequestsTimer() 1202 1203 for _, cb := range pc.callbacks.StatusUpdated { 1204 cb(StatusUpdatedEvent{ 1205 Event: PeerConnected, 1206 PeerId: pc.PeerID, 1207 }) 1208 } 1209 1210 err := pc.mainReadLoop() 1211 if err != nil { 1212 for _, cb := range pc.callbacks.StatusUpdated { 1213 cb(StatusUpdatedEvent{ 1214 Event: PeerDisconnected, 1215 Error: err, 1216 PeerId: pc.PeerID, 1217 }) 1218 } 1219 return fmt.Errorf("main read loop: %w", err) 1220 } 1221 return nil 1222 } 1223 1224 func (p *PeerConn) initUpdateRequestsTimer() { 1225 if check.Enabled { 1226 if p.updateRequestsTimer != nil { 1227 panic(p.updateRequestsTimer) 1228 } 1229 } 1230 if enableUpdateRequestsTimer { 1231 p.updateRequestsTimer = time.AfterFunc(math.MaxInt64, p.updateRequestsTimerFunc) 1232 } 1233 } 1234 1235 const peerUpdateRequestsTimerReason = "updateRequestsTimer" 1236 1237 func (c *PeerConn) updateRequestsTimerFunc() { 1238 c.locker().Lock() 1239 defer c.locker().Unlock() 1240 if c.closed.IsSet() { 1241 return 1242 } 1243 if c.isLowOnRequests() { 1244 // If there are no outstanding requests, then a request update should have already run. 1245 return 1246 } 1247 if d := time.Since(c.lastRequestUpdate); d < updateRequestsTimerDuration { 1248 // These should be benign, Timer.Stop doesn't guarantee that its function won't run if it's 1249 // already been fired. 1250 torrent.Add("spurious timer requests updates", 1) 1251 return 1252 } 1253 c.onNeedUpdateRequests(peerUpdateRequestsTimerReason) 1254 } 1255 1256 // Maximum pending requests we allow peers to send us. If peer requests are buffered on read, this 1257 // instructs the amount of memory that might be used to cache pending writes. Assuming 512KiB 1258 // (1<<19) cached for sending, for 16KiB (1<<14) chunks. 1259 const localClientReqq = 1024 1260 1261 // See the order given in Transmission's tr_peerMsgsNew. 1262 func (pc *PeerConn) sendInitialMessages() { 1263 t := pc.t 1264 cl := t.cl 1265 if pc.PeerExtensionBytes.SupportsExtended() && cl.config.Extensions.SupportsExtended() { 1266 pc.write(pp.Message{ 1267 Type: pp.Extended, 1268 ExtendedID: pp.HandshakeExtendedID, 1269 ExtendedPayload: func() []byte { 1270 msg := pp.ExtendedHandshakeMessage{ 1271 V: cl.config.ExtendedHandshakeClientVersion, 1272 Reqq: localClientReqq, 1273 YourIp: pp.CompactIp(pc.remoteIp()), 1274 Encryption: cl.config.HeaderObfuscationPolicy.Preferred || !cl.config.HeaderObfuscationPolicy.RequirePreferred, 1275 Port: cl.incomingPeerPort(), 1276 MetadataSize: t.metadataSize(), 1277 // TODO: We can figure these out specific to the socket used. 1278 Ipv4: pp.CompactIp(cl.config.PublicIp4.To4()), 1279 Ipv6: cl.config.PublicIp6.To16(), 1280 } 1281 msg.M = pc.LocalLtepProtocolMap.toSupportedExtensionDict() 1282 return bencode.MustMarshal(msg) 1283 }(), 1284 }) 1285 } 1286 func() { 1287 if pc.fastEnabled() { 1288 if t.haveAllPieces() { 1289 pc.write(pp.Message{Type: pp.HaveAll}) 1290 pc.sentHaves.AddRange(0, bitmap.BitRange(pc.t.NumPieces())) 1291 return 1292 } else if !t.haveAnyPieces() { 1293 pc.write(pp.Message{Type: pp.HaveNone}) 1294 pc.sentHaves.Clear() 1295 return 1296 } 1297 } 1298 pc.postBitfield() 1299 }() 1300 if pc.PeerExtensionBytes.SupportsDHT() && cl.config.Extensions.SupportsDHT() && cl.haveDhtServer() { 1301 pc.write(pp.Message{ 1302 Type: pp.Port, 1303 Port: cl.dhtPort(), 1304 }) 1305 } 1306 } 1307 1308 func (cl *Client) dhtPort() (ret uint16) { 1309 if len(cl.dhtServers) == 0 { 1310 return 1311 } 1312 return uint16(missinggo.AddrPort(cl.dhtServers[len(cl.dhtServers)-1].Addr())) 1313 } 1314 1315 func (cl *Client) haveDhtServer() bool { 1316 return len(cl.dhtServers) > 0 1317 } 1318 1319 // Process incoming ut_metadata message. 1320 func (cl *Client) gotMetadataExtensionMsg(payload []byte, t *Torrent, c *PeerConn) error { 1321 var d pp.ExtendedMetadataRequestMsg 1322 err := bencode.Unmarshal(payload, &d) 1323 if _, ok := err.(bencode.ErrUnusedTrailingBytes); ok { 1324 } else if err != nil { 1325 return fmt.Errorf("error unmarshalling bencode: %s", err) 1326 } 1327 piece := d.Piece 1328 switch d.Type { 1329 case pp.DataMetadataExtensionMsgType: 1330 c.modifyRelevantConnStats(add(1, func(cs *ConnStats) *Count { return &cs.MetadataChunksRead })) 1331 if !c.requestedMetadataPiece(piece) { 1332 return fmt.Errorf("got unexpected piece %d", piece) 1333 } 1334 c.metadataRequests[piece] = false 1335 begin := len(payload) - d.PieceSize() 1336 if begin < 0 || begin >= len(payload) { 1337 return fmt.Errorf("data has bad offset in payload: %d", begin) 1338 } 1339 t.saveMetadataPiece(piece, payload[begin:]) 1340 c.lastUsefulChunkReceived = time.Now() 1341 err = t.maybeCompleteMetadata() 1342 if err != nil { 1343 // Log this at the Torrent-level, as we don't partition metadata by Peer yet, so we 1344 // don't know who to blame. TODO: Also errors can be returned here that aren't related 1345 // to verifying metadata, which should be fixed. This should be tagged with metadata, so 1346 // log consumers can filter for this message. 1347 t.logger.WithDefaultLevel(log.Warning).Printf("error completing metadata: %v", err) 1348 } 1349 return err 1350 case pp.RequestMetadataExtensionMsgType: 1351 if !t.haveMetadataPiece(piece) { 1352 c.protocolLogger.WithDefaultLevel(log.Debug).Printf("rejecting metadata piece %d", piece) 1353 c.write(t.newMetadataExtensionMessage(c, pp.RejectMetadataExtensionMsgType, d.Piece, nil)) 1354 return nil 1355 } 1356 start := (1 << 14) * piece 1357 c.protocolLogger.WithDefaultLevel(log.Debug).Printf("sending metadata piece %d", piece) 1358 c.write(t.newMetadataExtensionMessage(c, pp.DataMetadataExtensionMsgType, piece, t.metadataBytes[start:start+t.metadataPieceSize(piece)])) 1359 return nil 1360 case pp.RejectMetadataExtensionMsgType: 1361 return nil 1362 default: 1363 return errors.New("unknown msg_type value") 1364 } 1365 } 1366 1367 func (cl *Client) badPeerAddr(addr PeerRemoteAddr) bool { 1368 if ipa, ok := tryIpPortFromNetAddr(addr); ok { 1369 return cl.badPeerIPPort(ipa.IP, ipa.Port) 1370 } 1371 return false 1372 } 1373 1374 // Returns whether the IP address and port are considered "bad". 1375 func (cl *Client) badPeerIPPort(ip net.IP, port int) bool { 1376 if port == 0 || ip == nil { 1377 return true 1378 } 1379 if cl.dopplegangerAddr(net.JoinHostPort(ip.String(), strconv.FormatInt(int64(port), 10))) { 1380 return true 1381 } 1382 if _, ok := cl.ipBlockRange(ip); ok { 1383 return true 1384 } 1385 ipAddr, ok := netip.AddrFromSlice(ip) 1386 if !ok { 1387 panic(ip) 1388 } 1389 if _, ok := cl.badPeerIPs[ipAddr]; ok { 1390 return true 1391 } 1392 return false 1393 } 1394 1395 // Return a Torrent ready for insertion into a Client. 1396 func (cl *Client) newTorrent(ih metainfo.Hash, specStorage storage.ClientImpl) (t *Torrent) { 1397 return cl.newTorrentOpt(AddTorrentOpts{ 1398 InfoHash: ih, 1399 Storage: specStorage, 1400 }) 1401 } 1402 1403 // Return a Torrent ready for insertion into a Client. This is also the method to call to create 1404 // Torrents for testing. 1405 func (cl *Client) newTorrentOpt(opts AddTorrentOpts) (t *Torrent) { 1406 var v1InfoHash g.Option[infohash.T] 1407 if !opts.InfoHash.IsZero() { 1408 v1InfoHash.Set(opts.InfoHash) 1409 } 1410 if !v1InfoHash.Ok && !opts.InfoHashV2.Ok { 1411 panic("v1 infohash must be nonzero or v2 infohash must be set") 1412 } 1413 // use provided storage, if provided 1414 storageClient := cl.defaultStorage 1415 if opts.Storage != nil { 1416 storageClient = storage.NewClient(opts.Storage) 1417 } 1418 1419 t = &Torrent{ 1420 cl: cl, 1421 infoHash: v1InfoHash, 1422 infoHashV2: opts.InfoHashV2, 1423 peers: prioritizedPeers{ 1424 om: gbtree.New(32), 1425 getPrio: func(p PeerInfo) peerPriority { 1426 ipPort := p.addr() 1427 return bep40PriorityIgnoreError(cl.publicAddr(ipPort.IP), ipPort) 1428 }, 1429 }, 1430 conns: make(map[*PeerConn]struct{}, 2*cl.config.EstablishedConnsPerTorrent), 1431 1432 storageOpener: storageClient, 1433 maxEstablishedConns: cl.config.EstablishedConnsPerTorrent, 1434 1435 metadataChanged: sync.Cond{ 1436 L: cl.locker(), 1437 }, 1438 gotMetainfoC: make(chan struct{}), 1439 1440 ignoreUnverifiedPieceCompletion: opts.IgnoreUnverifiedPieceCompletion, 1441 initialPieceCheckDisabled: opts.DisableInitialPieceCheck, 1442 } 1443 g.MakeMap(&t.webSeeds) 1444 t.closedCtx, t.closedCtxCancel = context.WithCancelCause(context.Background()) 1445 t.getInfoCtx, t.getInfoCtxCancel = context.WithCancelCause(t.closedCtx) 1446 var salt [8]byte 1447 rand.Read(salt[:]) 1448 t.smartBanCache.Hash = func(b []byte) uint64 { 1449 h := xxhash.New() 1450 h.Write(salt[:]) 1451 h.Write(b) 1452 return h.Sum64() 1453 } 1454 t.smartBanCache.Init() 1455 t.networkingEnabled.Set() 1456 ihHex := t.InfoHash().HexString() 1457 t.logger = cl.logger.WithDefaultLevel(log.Debug).WithNames(ihHex).WithContextText(ihHex) 1458 t.name() 1459 t.baseSlogger = cl.slogger 1460 if opts.ChunkSize == 0 { 1461 opts.ChunkSize = defaultChunkSize 1462 } 1463 t.setChunkSize(opts.ChunkSize) 1464 cl.torrents[t] = struct{}{} 1465 return 1466 } 1467 1468 // A file-like handle to some torrent data resource. 1469 type Handle interface { 1470 io.Reader 1471 io.Seeker 1472 io.Closer 1473 io.ReaderAt 1474 } 1475 1476 func (cl *Client) AddTorrentInfoHash(infoHash metainfo.Hash) (t *Torrent, new bool) { 1477 return cl.AddTorrentInfoHashWithStorage(infoHash, nil) 1478 } 1479 1480 // Deprecated. Adds a torrent by InfoHash with a custom Storage implementation. 1481 // If the torrent already exists then this Storage is ignored and the 1482 // existing torrent returned with `new` set to `false` 1483 func (cl *Client) AddTorrentInfoHashWithStorage( 1484 infoHash metainfo.Hash, 1485 specStorage storage.ClientImpl, 1486 ) (t *Torrent, new bool) { 1487 cl.lock() 1488 defer cl.unlock() 1489 t, ok := cl.torrentsByShortHash[infoHash] 1490 if ok { 1491 return 1492 } 1493 new = true 1494 1495 t = cl.newTorrent(infoHash, specStorage) 1496 cl.eachDhtServer(func(s DhtServer) { 1497 if cl.config.PeriodicallyAnnounceTorrentsToDht { 1498 go t.dhtAnnouncer(s) 1499 } 1500 }) 1501 cl.torrentsByShortHash[infoHash] = t 1502 cl.torrents[t] = struct{}{} 1503 cl.clearAcceptLimits() 1504 t.updateWantPeersEvent() 1505 // Tickle Client.waitAccept, new torrent may want conns. 1506 cl.event.Broadcast() 1507 return 1508 } 1509 1510 // Adds a torrent by InfoHash with a custom Storage implementation. If the torrent already exists 1511 // then this Storage is ignored and the existing torrent returned with `new` set to `false`. 1512 func (cl *Client) AddTorrentOpt(opts AddTorrentOpts) (t *Torrent, new bool) { 1513 infoHash := opts.InfoHash 1514 panicif.Zero(infoHash) 1515 cl.lock() 1516 defer cl.unlock() 1517 t, ok := cl.torrentsByShortHash[infoHash] 1518 if ok { 1519 return 1520 } 1521 if opts.InfoHashV2.Ok { 1522 t, ok = cl.torrentsByShortHash[*opts.InfoHashV2.Value.ToShort()] 1523 if ok { 1524 return 1525 } 1526 } 1527 new = true 1528 1529 t = cl.newTorrentOpt(opts) 1530 cl.eachDhtServer(func(s DhtServer) { 1531 if cl.config.PeriodicallyAnnounceTorrentsToDht { 1532 go t.dhtAnnouncer(s) 1533 } 1534 }) 1535 cl.torrentsByShortHash[infoHash] = t 1536 t.setInfoBytesLocked(opts.InfoBytes) 1537 cl.clearAcceptLimits() 1538 t.updateWantPeersEvent() 1539 // Tickle Client.waitAccept, new torrent may want conns. 1540 cl.event.Broadcast() 1541 return 1542 } 1543 1544 type AddTorrentOpts struct { 1545 InfoHash infohash.T 1546 InfoHashV2 g.Option[infohash_v2.T] 1547 Storage storage.ClientImpl 1548 // Only applied for new torrents (check Client.AddTorrent* method bool return value). If 0, the 1549 // default chunk size is used (16 KiB in current modern BitTorrent clients). 1550 ChunkSize pp.Integer 1551 InfoBytes []byte 1552 // Don't hash data if piece completion is missing. This is useful for very large torrents that 1553 // are dropped in place from an external source and trigger a lot of initial piece checks. 1554 DisableInitialPieceCheck bool 1555 // Require pieces to be checked as soon as info is available. This is because we have no way to 1556 // schedule an initial check only, and don't want to race against use of Torrent.Complete. 1557 IgnoreUnverifiedPieceCompletion bool 1558 // Whether to initially allow data download or upload 1559 DisallowDataUpload bool 1560 DisallowDataDownload bool 1561 } 1562 1563 // Add or merge a torrent spec. Returns new if the torrent wasn't already in the client. See also 1564 // Torrent.MergeSpec. 1565 func (cl *Client) AddTorrentSpec(spec *TorrentSpec) (t *Torrent, new bool, err error) { 1566 t, new = cl.AddTorrentOpt(spec.AddTorrentOpts) 1567 modSpec := *spec 1568 // ChunkSize was already applied by adding a new Torrent, and MergeSpec disallows changing it. 1569 modSpec.ChunkSize = 0 1570 err = t.MergeSpec(&modSpec) 1571 if err != nil && new { 1572 t.Drop() 1573 } 1574 return 1575 } 1576 1577 // The trackers will be merged with the existing ones. If the Info isn't yet known, it will be set. 1578 // The display name is replaced if the new spec provides one. Note that any `Storage` is ignored. 1579 // Many fields in the AddTorrentOpts field in TorrentSpec are ignored because the Torrent is already 1580 // added. 1581 func (t *Torrent) MergeSpec(spec *TorrentSpec) error { 1582 if spec.DisplayName != "" { 1583 t.SetDisplayName(spec.DisplayName) 1584 } 1585 if spec.InfoBytes != nil { 1586 err := t.SetInfoBytes(spec.InfoBytes) 1587 if err != nil { 1588 return err 1589 } 1590 } 1591 cl := t.cl 1592 cl.AddDhtNodes(spec.DhtNodes) 1593 t.AddSources(spec.Sources) 1594 // TODO: The lock should be moved earlier. 1595 cl.lock() 1596 defer cl.unlock() 1597 for _, url := range spec.Webseeds { 1598 t.addWebSeed(url) 1599 } 1600 t.addPeersIter(func(yield func(PeerInfo) bool) { 1601 for _, peerAddr := range spec.PeerAddrs { 1602 if !yield(PeerInfo{ 1603 Addr: StringAddr(peerAddr), 1604 Source: PeerSourceDirect, 1605 Trusted: true, 1606 }) { 1607 return 1608 } 1609 } 1610 }) 1611 if spec.ChunkSize != 0 { 1612 panic("chunk size cannot be changed for existing Torrent") 1613 } 1614 t.addTrackers(spec.Trackers) 1615 t.maybeNewConns() 1616 return errors.Join(t.addPieceLayersLocked(spec.PieceLayers)...) 1617 } 1618 1619 func (cl *Client) dropTorrent(t *Torrent, wg *sync.WaitGroup) { 1620 t.close(wg) 1621 } 1622 1623 func (cl *Client) allTorrentsCompleted() bool { 1624 for t := range cl.torrents { 1625 if !t.haveInfo() { 1626 return false 1627 } 1628 if !t.haveAllPieces() { 1629 return false 1630 } 1631 } 1632 return true 1633 } 1634 1635 // Returns true when all torrents are completely downloaded and false if the 1636 // client is stopped before that. 1637 func (cl *Client) WaitAll() bool { 1638 cl.lock() 1639 defer cl.unlock() 1640 for !cl.allTorrentsCompleted() { 1641 if cl.closed.IsSet() { 1642 return false 1643 } 1644 cl.event.Wait() 1645 } 1646 return true 1647 } 1648 1649 // Returns handles to all the torrents loaded in the Client. 1650 func (cl *Client) Torrents() []*Torrent { 1651 cl.rLock() 1652 defer cl.rUnlock() 1653 return cl.torrentsAsSlice() 1654 } 1655 1656 func (cl *Client) torrentsAsSlice() (ret []*Torrent) { 1657 ret = make([]*Torrent, 0, len(cl.torrents)) 1658 for t := range cl.torrents { 1659 ret = append(ret, t) 1660 } 1661 return 1662 } 1663 1664 func (cl *Client) AddMagnet(uri string) (T *Torrent, err error) { 1665 spec, err := TorrentSpecFromMagnetUri(uri) 1666 if err != nil { 1667 return 1668 } 1669 T, _, err = cl.AddTorrentSpec(spec) 1670 return 1671 } 1672 1673 func (cl *Client) AddTorrent(mi *metainfo.MetaInfo) (T *Torrent, err error) { 1674 ts, err := TorrentSpecFromMetaInfoErr(mi) 1675 if err != nil { 1676 return 1677 } 1678 T, _, err = cl.AddTorrentSpec(ts) 1679 return 1680 } 1681 1682 func (cl *Client) AddTorrentFromFile(filename string) (T *Torrent, err error) { 1683 mi, err := metainfo.LoadFromFile(filename) 1684 if err != nil { 1685 return 1686 } 1687 return cl.AddTorrent(mi) 1688 } 1689 1690 func (cl *Client) DhtServers() []DhtServer { 1691 return cl.dhtServers 1692 } 1693 1694 func (cl *Client) AddDhtNodes(nodes []string) { 1695 for _, n := range nodes { 1696 hmp := missinggo.SplitHostMaybePort(n) 1697 ip := net.ParseIP(hmp.Host) 1698 if ip == nil { 1699 cl.logger.Printf("won't add DHT node with bad IP: %q", hmp.Host) 1700 continue 1701 } 1702 ni := krpc.NodeInfo{ 1703 Addr: krpc.NodeAddr{ 1704 IP: ip, 1705 Port: hmp.Port, 1706 }, 1707 } 1708 cl.eachDhtServer(func(s DhtServer) { 1709 s.AddNode(ni) 1710 }) 1711 } 1712 } 1713 1714 func (cl *Client) banPeerIP(ip net.IP) { 1715 // We can't take this from string, because it will lose netip's v4on6. net.ParseIP parses v4 1716 // addresses directly to v4on6, which doesn't compare equal with v4. 1717 ipAddr, ok := netip.AddrFromSlice(ip) 1718 panicif.False(ok) 1719 g.MakeMapIfNil(&cl.badPeerIPs) 1720 cl.badPeerIPs[ipAddr] = struct{}{} 1721 for t := range cl.torrents { 1722 t.iterPeers(func(p *Peer) { 1723 if p.remoteIp().Equal(ip) { 1724 t.slogger().Debug("dropping peer with banned ip", "peer", p, "ip", ip) 1725 // Should this be a close? 1726 p.drop() 1727 } 1728 }) 1729 } 1730 } 1731 1732 type newConnectionOpts struct { 1733 outgoing bool 1734 remoteAddr PeerRemoteAddr 1735 localPublicAddr peerLocalPublicAddr 1736 network string 1737 connString string 1738 } 1739 1740 func (cl *Client) newConnection(nc net.Conn, opts newConnectionOpts) (c *PeerConn) { 1741 if opts.network == "" { 1742 panic(opts.remoteAddr) 1743 } 1744 c = &PeerConn{ 1745 Peer: Peer{ 1746 cl: cl, 1747 outgoing: opts.outgoing, 1748 choking: true, 1749 peerChoking: true, 1750 1751 RemoteAddr: opts.remoteAddr, 1752 localPublicAddr: opts.localPublicAddr, 1753 Network: opts.network, 1754 callbacks: &cl.config.Callbacks, 1755 }, 1756 PeerMaxRequests: 250, 1757 connString: opts.connString, 1758 conn: nc, 1759 } 1760 c.initRequestState() 1761 // TODO: Need to be much more explicit about this, including allowing non-IP bannable addresses. 1762 if opts.remoteAddr != nil { 1763 netipAddrPort, err := netip.ParseAddrPort(opts.remoteAddr.String()) 1764 if err == nil { 1765 c.bannableAddr = Some(netipAddrPort.Addr()) 1766 } 1767 } 1768 c.legacyPeerImpl = c 1769 c.peerImpl = c 1770 c.setPeerLoggers(cl.logger, cl.slogger) 1771 c.setRW(connStatsReadWriter{nc, c}) 1772 c.r = cl.newDownloadRateLimitedReader(c.r) 1773 c.logger.Levelf( 1774 log.Debug, 1775 "inited with remoteAddr %v network %v outgoing %t", 1776 opts.remoteAddr, opts.network, opts.outgoing, 1777 ) 1778 for _, f := range cl.config.Callbacks.NewPeer { 1779 f(&c.Peer) 1780 } 1781 return 1782 } 1783 1784 func (cl *Client) newDownloadRateLimitedReader(r io.Reader) io.Reader { 1785 return newRateLimitedReader(r, cl.config.DownloadRateLimiter) 1786 } 1787 1788 func (cl *Client) onDHTAnnouncePeer(ih metainfo.Hash, ip net.IP, port int, portOk bool) { 1789 cl.lock() 1790 defer cl.unlock() 1791 t := cl.torrentsByShortHash[ih] 1792 if t == nil { 1793 return 1794 } 1795 t.addPeers([]PeerInfo{{ 1796 Addr: ipPortAddr{ip, port}, 1797 Source: PeerSourceDhtAnnouncePeer, 1798 }}) 1799 } 1800 1801 func firstNotNil(ips ...net.IP) net.IP { 1802 for _, ip := range ips { 1803 if ip != nil { 1804 return ip 1805 } 1806 } 1807 return nil 1808 } 1809 1810 func (cl *Client) eachListener(f func(Listener) bool) { 1811 for _, s := range cl.listeners { 1812 if !f(s) { 1813 break 1814 } 1815 } 1816 } 1817 1818 func (cl *Client) findListener(f func(Listener) bool) (ret Listener) { 1819 for i := 0; i < len(cl.listeners); i += 1 { 1820 if ret = cl.listeners[i]; f(ret) { 1821 return 1822 } 1823 } 1824 return nil 1825 } 1826 1827 func (cl *Client) publicIp(peer net.IP) net.IP { 1828 // TODO: Use BEP 10 to determine how peers are seeing us. 1829 if peer.To4() != nil { 1830 return firstNotNil( 1831 cl.config.PublicIp4, 1832 cl.findListenerIp(func(ip net.IP) bool { return ip.To4() != nil }), 1833 ) 1834 } 1835 1836 return firstNotNil( 1837 cl.config.PublicIp6, 1838 cl.findListenerIp(func(ip net.IP) bool { return ip.To4() == nil }), 1839 ) 1840 } 1841 1842 func (cl *Client) findListenerIp(f func(net.IP) bool) net.IP { 1843 l := cl.findListener( 1844 func(l Listener) bool { 1845 return f(addrIpOrNil(l.Addr())) 1846 }, 1847 ) 1848 if l == nil { 1849 return nil 1850 } 1851 return addrIpOrNil(l.Addr()) 1852 } 1853 1854 // Our IP as a peer should see it. 1855 func (cl *Client) publicAddr(peer net.IP) IpPort { 1856 return IpPort{IP: cl.publicIp(peer), Port: uint16(cl.incomingPeerPort())} 1857 } 1858 1859 // ListenAddrs addresses currently being listened to. 1860 func (cl *Client) ListenAddrs() (ret []net.Addr) { 1861 cl.lock() 1862 ret = make([]net.Addr, len(cl.listeners)) 1863 for i := 0; i < len(cl.listeners); i += 1 { 1864 ret[i] = cl.listeners[i].Addr() 1865 } 1866 cl.unlock() 1867 return 1868 } 1869 1870 func (cl *Client) PublicIPs() (ips []net.IP) { 1871 if ip := cl.config.PublicIp4; ip != nil { 1872 ips = append(ips, ip) 1873 } 1874 if ip := cl.config.PublicIp6; ip != nil { 1875 ips = append(ips, ip) 1876 } 1877 return 1878 } 1879 1880 func (cl *Client) onBadAccept(addr PeerRemoteAddr) { 1881 ipa, ok := tryIpPortFromNetAddr(addr) 1882 if !ok { 1883 return 1884 } 1885 ip := maskIpForAcceptLimiting(ipa.IP) 1886 if cl.acceptLimiter == nil { 1887 cl.acceptLimiter = make(map[ipStr]int) 1888 } 1889 cl.acceptLimiter[ipStr(ip.String())]++ 1890 } 1891 1892 func maskIpForAcceptLimiting(ip net.IP) net.IP { 1893 if ip4 := ip.To4(); ip4 != nil { 1894 return ip4.Mask(net.CIDRMask(24, 32)) 1895 } 1896 return ip 1897 } 1898 1899 func (cl *Client) clearAcceptLimits() { 1900 cl.acceptLimiter = nil 1901 } 1902 1903 func (cl *Client) acceptLimitClearer() { 1904 for { 1905 select { 1906 case <-cl.closed.Done(): 1907 return 1908 case <-time.After(15 * time.Minute): 1909 cl.lock() 1910 cl.clearAcceptLimits() 1911 cl.unlock() 1912 } 1913 } 1914 } 1915 1916 func (cl *Client) rateLimitAccept(ip net.IP) bool { 1917 if cl.config.DisableAcceptRateLimiting { 1918 return false 1919 } 1920 return cl.acceptLimiter[ipStr(maskIpForAcceptLimiting(ip).String())] > 0 1921 } 1922 1923 func (cl *Client) rLock() { 1924 cl._mu.RLock() 1925 } 1926 1927 func (cl *Client) rUnlock() { 1928 cl._mu.RUnlock() 1929 } 1930 1931 func (cl *Client) lock() { 1932 cl._mu.Lock() 1933 } 1934 1935 func (cl *Client) unlock() { 1936 cl._mu.Unlock() 1937 } 1938 1939 func (cl *Client) locker() *lockWithDeferreds { 1940 return &cl._mu 1941 } 1942 1943 func (cl *Client) String() string { 1944 return fmt.Sprintf("<%[1]T %[1]p>", cl) 1945 } 1946 1947 func (cl *Client) ICEServers() []webrtc.ICEServer { 1948 var ICEServers []webrtc.ICEServer 1949 if cl.config.ICEServerList != nil { 1950 ICEServers = cl.config.ICEServerList 1951 } else if cl.config.ICEServers != nil { 1952 ICEServers = []webrtc.ICEServer{{URLs: cl.config.ICEServers}} 1953 } 1954 return ICEServers 1955 } 1956 1957 // Returns connection-level aggregate connStats at the Client level. See the comment on 1958 // TorrentStats.ConnStats. You probably want Client.Stats() instead. 1959 func (cl *Client) ConnStats() ConnStats { 1960 return cl.connStats.ConnStats.Copy() 1961 } 1962 1963 func (cl *Client) Stats() ClientStats { 1964 cl.rLock() 1965 defer cl.rUnlock() 1966 return cl.statsLocked() 1967 } 1968 1969 func (cl *Client) underWebSeedHttpRequestLimit(key webseedHostKeyHandle) bool { 1970 panicif.Zero(key) 1971 return cl.numWebSeedRequests[key] < webseedHostRequestConcurrency 1972 } 1973 1974 // Check for bad arrangements. This is a candidate for an error state check method. 1975 func (cl *Client) checkConfig() error { 1976 if EffectiveDownloadRateLimit(cl.config.DownloadRateLimiter) == 0 { 1977 if len(cl.dialers) != 0 { 1978 return errors.New("download rate limit is zero, but dialers are set") 1979 } 1980 if len(cl.listeners) != 0 && cl.config.AcceptPeerConnections { 1981 return errors.New("download rate limit is zero, but listening for peer connections") 1982 } 1983 } 1984 return nil 1985 } 1986 1987 var maxActivePieceHashers = initIntFromEnv("TORRENT_MAX_ACTIVE_PIECE_HASHERS", runtime.NumCPU(), 0) 1988 1989 func (cl *Client) maxActivePieceHashers() int { 1990 return maxActivePieceHashers 1991 } 1992 1993 func (cl *Client) belowMaxActivePieceHashers() bool { 1994 return cl.activePieceHashers < cl.maxActivePieceHashers() 1995 } 1996 1997 func (cl *Client) canStartPieceHashers() bool { 1998 return cl.belowMaxActivePieceHashers() 1999 } 2000 2001 func (cl *Client) startPieceHashers() { 2002 if !cl.canStartPieceHashers() { 2003 return 2004 } 2005 ts := make([]*Torrent, 0, len(cl.torrents)) 2006 for t := range cl.torrents { 2007 if !t.considerStartingHashers() { 2008 continue 2009 } 2010 ts = append(ts, t) 2011 } 2012 // Sort largest torrents first, as those are preferred by webseeds, and will cause less thrashing. 2013 slices.SortFunc(ts, func(a, b *Torrent) int { 2014 return -cmp.Compare(a.length(), b.length()) 2015 }) 2016 for _, t := range ts { 2017 t.startPieceHashers() 2018 if !cl.canStartPieceHashers() { 2019 break 2020 } 2021 } 2022 }