// The 'nodes' response is a string with fixed length contacts concatenated arbitrarily. func parseNodesString(nodes string, proto string) (parsed map[string]string) { var nodeContactLen int if proto == "udp4" { nodeContactLen = v4nodeContactLen } else if proto == "udp6" { nodeContactLen = v6nodeContactLen } else { return } parsed = make(map[string]string) if len(nodes)%nodeContactLen > 0 { log.V(3).Infof("DHT: len(NodeString) = %d, INVALID LENGTH, should be a multiple of %d", len(nodes), nodeContactLen) log.V(5).Infof("%T %#v\n", nodes, nodes) return } else { log.V(5).Infof("DHT: len(NodeString) = %d, had %d nodes, nodeContactLen=%d\n", len(nodes), len(nodes)/nodeContactLen, nodeContactLen) } for i := 0; i < len(nodes); i += nodeContactLen { id := nodes[i : i+nodeIdLen] address := nettools.BinaryToDottedPort(nodes[i+nodeIdLen : i+nodeContactLen]) parsed[id] = address } return }
func stringToPeers(in string) (peers []string) { peers = make([]string, 0) for i := 0; i < len(in); i += PEER_LEN { peer := nettools.BinaryToDottedPort(in[i : i+PEER_LEN]) peers = append(peers, peer) } return }
func (r *routingTable) kill(n *remoteNode, p *peerStore) { delete(r.addresses, n.address.String()) r.nTree.cut(InfoHash(n.id), 0) totalKilledNodes.Add(1) if r.boundaryNode != nil && n.id == r.boundaryNode.id { r.resetNeighborhoodBoundary() } p.killContact(nettools.BinaryToDottedPort(n.addressBinaryFormat)) }
func getTrackerInfo(url string) (tr *TrackerResponse, err error) { r, err := proxyHttpGet(url) if err != nil { return } defer r.Body.Close() if r.StatusCode >= 400 { data, _ := ioutil.ReadAll(r.Body) reason := "Bad Request " + string(data) log.Println(reason) err = errors.New(reason) return } var tr2 TrackerResponse err = bencode.NewDecoder(r.Body).Decode(&tr2) r.Body.Close() if err != nil { return } // Decode peers if len(tr2.PeersRaw) > 0 { const peerLen = 6 nPeers := len(tr2.PeersRaw) / peerLen // log.Println("Tracker gave us", nPeers, "peers") tr2.Peers = make([]string, nPeers) for i := 0; i < nPeers; i++ { peer := nettools.BinaryToDottedPort(tr2.PeersRaw[i*peerLen : (i+1)*peerLen]) tr2.Peers[i/peerLen] = peer } } // Decode peers6 if len(tr2.Peers6Raw) > 0 { const peerLen = 18 nPeers := len(tr2.Peers6Raw) / peerLen // log.Println("Tracker gave us", nPeers, "IPv6 peers") tr2.Peers6 = make([]string, nPeers) for i := 0; i < nPeers; i++ { peerEntry := tr2.Peers6Raw[i*peerLen : (i+1)*peerLen] host := net.IP(peerEntry[0:16]) port := int((uint(peerEntry[16]) << 8) | uint(peerEntry[17])) peer := net.JoinHostPort(host.String(), strconv.Itoa(port)) tr2.Peers6[i] = peer } } tr = &tr2 return }
func (r *routingTable) addNewNeighbor(n *remoteNode, displaceBoundary bool, proto string) { if err := r.insert(n, proto); err != nil { log.V(3).Infof("addNewNeighbor error: %v", err) return } if displaceBoundary && r.boundaryNode != nil { // This will also take care of setting a new boundary. r.kill(r.boundaryNode) } else { r.resetNeighborhoodBoundary() } log.V(4).Infof("New neighbor added %s with proximity %d", nettools.BinaryToDottedPort(n.addressBinaryFormat), r.proximity) }
// The 'nodes' response is a string with fixed length contacts concatenated arbitrarily. func parseNodesString(nodes string) (parsed map[string]string) { parsed = make(map[string]string) if len(nodes)%nodeContactLen > 0 { l4g.Info("DHT: Invalid length of nodes.") l4g.Info("DHT: Should be a multiple of %d, got %d", nodeContactLen, len(nodes)) return } for i := 0; i < len(nodes); i += nodeContactLen { id := nodes[i : i+nodeIdLen] address := nettools.BinaryToDottedPort(nodes[i+nodeIdLen : i+nodeContactLen]) parsed[id] = address } return }
// DecodePeerAddress transforms the binary-encoded host:port address into a // human-readable format. So, "abcdef" becomes 97.98.99.100:25958. func DecodePeerAddress(x string) string { return nettools.BinaryToDottedPort(x) }
func (t *TorrentSession) DoTorrent() (err error) { t.lastHeartBeat = time.Now() go t.deadlockDetector() log.Println("Fetching torrent.") rechokeChan := time.Tick(1 * time.Second) // Start out polling tracker every 20 seconds untill we get a response. // Maybe be exponential backoff here? retrackerChan := time.Tick(20 * time.Second) keepAliveChan := time.Tick(60 * time.Second) t.trackerInfoChan = make(chan *TrackerResponse) conChan := make(chan net.Conn) if useProxy() { // Only listen for peer connections if not using a proxy. t.listenForPeerConnections(conChan) } if t.m.Info.Private != 1 && useDHT { t.dht.PeersRequest(t.m.InfoHash, true) } t.fetchTrackerInfo("started") for { select { case _ = <-retrackerChan: if !trackerLessMode { t.fetchTrackerInfo("") } case dhtInfoHashPeers := <-t.dht.PeersRequestResults: newPeerCount := 0 // key = infoHash. The torrent client currently only // supports one download at a time, so let's assume // it's the case. for _, peers := range dhtInfoHashPeers { for _, peer := range peers { peer = dht.DecodePeerAddress(peer) if _, ok := t.peers[peer]; !ok { newPeerCount++ go connectToPeer(peer, conChan) } } } // log.Println("Contacting", newPeerCount, "new peers (thanks DHT!)") case ti := <-t.trackerInfoChan: t.ti = ti log.Println("Torrent has", t.ti.Complete, "seeders and", t.ti.Incomplete, "leachers.") if !trackerLessMode { peers := t.ti.Peers log.Println("Tracker gave us", len(peers)/6, "peers") newPeerCount := 0 for i := 0; i < len(peers); i += 6 { peer := nettools.BinaryToDottedPort(peers[i : i+6]) if _, ok := t.peers[peer]; !ok { newPeerCount++ go connectToPeer(peer, conChan) } } log.Println("Contacting", newPeerCount, "new peers") interval := t.ti.Interval if interval < 120 { interval = 120 } else if interval > 24*3600 { interval = 24 * 3600 } log.Println("..checking again in", interval, "seconds.") retrackerChan = time.Tick(interval * time.Second) log.Println("Contacting", newPeerCount, "new peers") } interval := t.ti.Interval if interval < 120 { interval = 120 } else if interval > 24*3600 { interval = 24 * 3600 } log.Println("..checking again in", interval.String()) retrackerChan = time.Tick(interval * time.Second) case pm := <-t.peerMessageChan: peer, message := pm.peer, pm.message peer.lastReadTime = time.Now() err2 := t.DoMessage(peer, message) if err2 != nil { if err2 != io.EOF { log.Println("Closing peer", peer.address, "because", err2) } t.ClosePeer(peer) } case conn := <-conChan: t.AddPeer(conn) case _ = <-rechokeChan: // TODO: recalculate who to choke / unchoke t.lastHeartBeat = time.Now() ratio := float64(0.0) if t.si.Downloaded > 0 { ratio = float64(t.si.Uploaded) / float64(t.si.Downloaded) } log.Println("Peers:", len(t.peers), "downloaded:", t.si.Downloaded, "uploaded:", t.si.Uploaded, "ratio", ratio) log.Println("good, total", t.goodPieces, t.totalPieces) if len(t.peers) < TARGET_NUM_PEERS && t.goodPieces < t.totalPieces { if t.m.Info.Private != 1 && useDHT { go t.dht.PeersRequest(t.m.InfoHash, true) } if !trackerLessMode { if t.ti == nil || t.ti.Complete > 100 { t.fetchTrackerInfo("") } } } case _ = <-keepAliveChan: now := time.Now() for _, peer := range t.peers { if peer.lastReadTime.Second() != 0 && now.Sub(peer.lastReadTime) > 3*time.Minute { // log.Println("Closing peer", peer.address, "because timed out.") t.ClosePeer(peer) continue } err2 := t.doCheckRequests(peer) if err2 != nil { if err2 != io.EOF { log.Println("Closing peer", peer.address, "because", err2) } t.ClosePeer(peer) continue } peer.keepAlive(now) } } } return }
// Requires Internet access and can be flaky if the server or the internet is // slow. func TestDHTLarge(t *testing.T) { if testing.Short() { t.Skip("TestDHTLarge requires internet access and can be flaky. Skipping in short mode.") } defer stats(t) c := NewConfig() c.SaveRoutingTable = false node, err := New(c) if err != nil { t.Fatalf("dht New: %v", err) } if err = node.Start(); err != nil { t.Fatalf("node.Run: %v", err) } realDHTNodes := []string{ "1.a.magnets.im", "router.utorrent.com", } for _, addr := range realDHTNodes { ip, err := net.LookupHost(addr) if err != nil { t.Error(err) continue } node.AddNode(ip[0] + ":6881") } // Test that we can reach at least one node. success := false var ( reachable int v expvar.Var ) for i := 0; i < 10; i++ { v = expvar.Get("totalNodesReached") reachable, err = strconv.Atoi(v.String()) if err != nil { t.Errorf("totalNodesReached conversion to int failed: %v", err) continue } if reachable > 0 { t.Logf("Contacted %d DHT nodes.", reachable) success = true break } time.Sleep(time.Second) } if !success { t.Fatal("No external DHT node could be contacted.") } // Test that we can find peers for a known torrent in a timely fashion. // // Torrent from: http://www.clearbits.net/torrents/244-time-management-for-anarchists-1 infoHash := InfoHash("\xb4\x62\xc0\xa8\xbc\xef\x1c\xe5\xbb\x56\xb9\xfd\xb8\xcf\x37\xff\xd0\x2f\x5f\x59") go node.PeersRequest(string(infoHash), true) var infoHashPeers map[InfoHash][]string select { case infoHashPeers = <-node.PeersRequestResults: t.Logf("Found %d peers.", len(infoHashPeers[infoHash])) case <-time.Tick(10 * time.Second): t.Fatal("Could not find new peers: timed out") } for ih, peers := range infoHashPeers { if infoHash != ih { t.Fatal("Unexpected infohash returned") } if len(peers) == 0 { t.Fatal("Could not find new torrent peers.") } for _, peer := range peers { t.Logf("peer found: %v", nettools.BinaryToDottedPort(peer)) } } }
// Process another node's response to a find_node query. func (d *DHT) processFindNodeResults(node *remoteNode, resp responseType) { var nodelist string totalRecvFindNodeReply.Add(1) query, _ := node.pendingQueries[resp.T] if d.config.UDPProto == "udp4" { nodelist = resp.R.Nodes } else if d.config.UDPProto == "udp6" { nodelist = resp.R.Nodes6 } log.V(5).Infof("processFindNodeResults find_node = %s len(nodelist)=%d", nettools.BinaryToDottedPort(node.addressBinaryFormat), len(nodelist)) if nodelist != "" { for id, address := range parseNodesString(nodelist, d.config.UDPProto) { _, addr, existed, err := d.routingTable.hostPortToNode(address, d.config.UDPProto) if err != nil { log.V(3).Infof("DHT error parsing node from find_find response: %v", err) continue } if id == d.nodeId { log.V(5).Infof("DHT got reference of self for find_node, id %x", id) continue } if addr == node.address.String() { // SelfPromotions are more common for find_node. They are // happening even for router.bittorrent.com totalSelfPromotions.Add(1) continue } if existed { if log.V(4) { x := hashDistance(query.ih, InfoHash(node.id)) log.Infof("DHT: processFindNodeResults DUPE node reference, query %x: %x@%v from %x@%v. Distance: %x.", query.ih, id, address, node.id, node.address, x) } totalFindNodeDupes.Add(1) } else { if log.V(4) { x := hashDistance(query.ih, InfoHash(node.id)) log.Infof("DHT: Got new node reference, query %x: %x@%v from %x@%v. Distance: %x.", query.ih, id, address, node.id, node.address, x) } // Includes the node in the routing table and ignores errors. // // Only continue the search if we really have to. r, err := d.routingTable.getOrCreateNode(id, addr, d.config.UDPProto) if err != nil { log.Warningf("processFindNodeResults calling getOrCreateNode: %v. Id=%x, Address=%q", err, id, addr) continue } if d.needMoreNodes() { select { case d.nodesRequest <- ihReq{query.ih, false}: default: // Too many find_node commands queued up. Dropping // this. The node has already been added to the // routing table so we're not losing any // information. } } d.getMorePeers(r) } } } }
func (ts *TorrentSession) DoTorrent() { ts.heartbeat = make(chan bool, 1) if ts.flags.UseDeadlockDetector { go ts.deadlockDetector() } if ts.flags.Cacher != nil && ts.fileStore != nil { cache := ts.flags.Cacher.NewCache(ts.M.InfoHash, ts.totalPieces, int(ts.M.Info.PieceLength), ts.totalSize) ts.fileStore.SetCache(cache) } heartbeatDuration := 1 * time.Second heartbeatChan := time.Tick(heartbeatDuration) keepAliveChan := time.Tick(60 * time.Second) var retrackerChan <-chan time.Time ts.hintNewPeerChan = make(chan string) ts.addPeerChan = make(chan *BtConn) if !ts.trackerLessMode { // Start out polling tracker every 20 seconds until we get a response. // Maybe be exponential backoff here? retrackerChan = time.Tick(20 * time.Second) ts.trackerInfoChan = make(chan *TrackerResponse) ts.trackerReportChan = make(chan ClientStatusReport) startTrackerClient(ts.flags.Dial, ts.M.Announce, ts.M.AnnounceList, ts.trackerInfoChan, ts.trackerReportChan) } if ts.Session.UseDHT { ts.dht.PeersRequest(ts.M.InfoHash, true) } if !ts.trackerLessMode && ts.Session.HaveTorrent { ts.fetchTrackerInfo("started") } defer ts.Shutdown() lastDownloaded := ts.Session.Downloaded for { if !ts.execOnSeedingDone && ts.goodPieces == ts.totalPieces { ts.execOnSeeding() ts.execOnSeedingDone = true } select { case <-ts.chokePolicyHeartbeat: ts.chokePeers() case hintNewPeer := <-ts.hintNewPeerChan: ts.tryNewPeer(hintNewPeer) case btconn := <-ts.addPeerChan: ts.addPeerImp(btconn) case <-retrackerChan: if !ts.trackerLessMode { ts.fetchTrackerInfo("") } case ti := <-ts.trackerInfoChan: ts.ti = ti log.Println("[", ts.M.Info.Name, "] Torrent has", ts.ti.Complete, "seeders and", ts.ti.Incomplete, "leachers") if !ts.trackerLessMode { newPeerCount := 0 { peers := ts.ti.Peers if len(peers) > 0 { const peerLen = 6 log.Println("[", ts.M.Info.Name, "] Tracker gave us", len(peers)/peerLen, "peers") for i := 0; i < len(peers); i += peerLen { peer := nettools.BinaryToDottedPort(peers[i : i+peerLen]) if ts.tryNewPeer(peer) { newPeerCount++ } } } } { peers6 := ts.ti.Peers6 if len(peers6) > 0 { const peerLen = 18 log.Println("[", ts.M.Info.Name, "] Tracker gave us", len(peers6)/peerLen, "IPv6 peers") for i := 0; i < len(peers6); i += peerLen { peerEntry := peers6[i : i+peerLen] host := net.IP(peerEntry[0:16]) port := int((uint(peerEntry[16]) << 8) | uint(peerEntry[17])) peer := net.JoinHostPort(host.String(), strconv.Itoa(port)) if ts.tryNewPeer(peer) { newPeerCount++ } } } } log.Println("[", ts.M.Info.Name, "] Contacting", newPeerCount, "new peers") } interval := ts.ti.Interval minInterval := uint(120) maxInterval := uint(24 * 3600) if interval < minInterval { interval = minInterval } else if interval > maxInterval { interval = maxInterval } log.Println("[", ts.M.Info.Name, "] ..checking again in", interval, "seconds") retrackerChan = time.Tick(time.Duration(interval) * time.Second) case pm := <-ts.peerMessageChan: peer, message := pm.peer, pm.message peer.lastReadTime = time.Now() err2 := ts.DoMessage(peer, message) if err2 != nil { if err2 != io.EOF { log.Println("[", ts.M.Info.Name, "] Closing peer", peer.address, "because", err2) } ts.ClosePeer(peer) } case <-heartbeatChan: if ts.flags.UseDeadlockDetector { ts.heartbeat <- true } ratio := float64(0.0) if ts.Session.Downloaded > 0 { ratio = float64(ts.Session.Uploaded) / float64(ts.Session.Downloaded) } speed := humanSize(float64(ts.Session.Downloaded-lastDownloaded) / heartbeatDuration.Seconds()) lastDownloaded = ts.Session.Downloaded log.Printf("[ %s ] Peers: %d downloaded: %d (%s/s) uploaded: %d ratio: %f pieces: %d/%d\n", ts.M.Info.Name, len(ts.peers), ts.Session.Downloaded, speed, ts.Session.Uploaded, ratio, ts.goodPieces, ts.totalPieces) if ts.totalPieces != 0 && ts.goodPieces == ts.totalPieces && ratio >= ts.flags.SeedRatio { log.Println("[", ts.M.Info.Name, "] Achieved target seed ratio", ts.flags.SeedRatio) return } if len(ts.peers) < TARGET_NUM_PEERS && (ts.totalPieces == 0 || ts.goodPieces < ts.totalPieces) { if ts.Session.UseDHT { go ts.dht.PeersRequest(ts.M.InfoHash, true) } if !ts.trackerLessMode { if ts.ti == nil || ts.ti.Complete > 100 { ts.fetchTrackerInfo("") } } } case <-keepAliveChan: now := time.Now() for _, peer := range ts.peers { if peer.lastReadTime.Second() != 0 && now.Sub(peer.lastReadTime) > 3*time.Minute { // log.Println("[", ts.M.Info.Name, "] Closing peer", peer.address, "because timed out") ts.ClosePeer(peer) continue } err2 := ts.doCheckRequests(peer) if err2 != nil { if err2 != io.EOF { log.Println("[", ts.M.Info.Name, "] Closing peer", peer.address, "because", err2) } ts.ClosePeer(peer) continue } peer.keepAlive(now) } case <-ts.quit: log.Println("[", ts.M.Info.Name, "] Quitting torrent session") return } } }
func (t *TorrentSession) DoTorrent() (err error) { t.lastHeartBeat = time.Now() log.Println("Fetching torrent.") rechokeChan := time.Tick(time.Duration(cfg.rechokeTick) * time.Second) // Start out polling tracker every 20 seconds untill we get a response. // Maybe be exponential backoff here? retrackerChan := time.Tick(20 * time.Second) keepAliveChan := time.Tick(60 * time.Second) t.trackerInfoChan = make(chan *TrackerResponse) conChan := make(chan net.Conn) if !useProxy() { // Only listen for peer connections if not using a proxy. t.listenForPeerConnections(conChan) } DHTPeersRequestResults := make(chan map[string][]string) if t.m.Info.Private != 1 && cfg.useDHT { DHTPeersRequestResults = t.dht.PeersRequestResults t.dht.PeersRequest(t.m.InfoHash, true) } if !t.isSeeding() { t.fetchTrackerInfo("completed") }else{ t.fetchTrackerInfo("started") } for { select { case _ = <-retrackerChan: if !cfg.trackerLessMode { t.fetchTrackerInfo("") } case dhtInfoHashPeers := <-DHTPeersRequestResults: newPeerCount := 0 // key = infoHash. The torrent client currently only // supports one download at a time, so let's assume // it's the case. for _, peers := range dhtInfoHashPeers { for _, peer := range peers { peer = nettools.BinaryToDottedPort(peer) if !t.PeerExist(peer) { newPeerCount++ go connectToPeer(peer, conChan) } } } // log.Println("Contacting", newPeerCount, "new peers (thanks DHT!)") case ti := <-t.trackerInfoChan: if t.isSeeding() { break } t.ti = ti //log.Println("Torrent has", t.ti.Complete, "seeders and", t.ti.Incomplete, "leachers.") if !cfg.trackerLessMode { peers := t.ti.Peers //log.Println("Tracker gave us", len(peers)/6, "peers") newPeerCount := 0 for i := 0; i < len(peers); i += 6 { peer := nettools.BinaryToDottedPort(peers[i : i+6]) //log.Printf("trackerInfoChan %v\n", peer) selfIp, err := GetLocalIP(); if err != nil{ log.Println(err) panic("") } if !t.PeerExist(peer) && !strings.Contains(peer, selfIp) && t.PeerCount() < cfg.TARGET_NUM_PEERS{ newPeerCount++ go connectToPeer(peer, conChan) } } //log.Println("Contacting", newPeerCount, "new peers") interval := t.ti.Interval //log.Println("..checking again in", interval, "seconds.") retrackerChan = time.Tick(interval * time.Second) //log.Println("Contacting", newPeerCount, "new peers") } interval := t.ti.Interval //log.Println("..checking again in", interval.String()) retrackerChan = time.Tick(interval * time.Second) case pm := <-t.peerMessageChan: peer, message := pm.peer, pm.message peer.lastReadTime = time.Now() err2 := t.DoMessage(peer, message) if err2 != nil { if err2 != io.EOF { log.Println("Closing peer", peer.address, "because", err2) } t.ClosePeer(peer) } case conn := <-conChan: t.AddPeer(conn) case ioResult := <-t.ioResponceChan: if _, ok := ioResult.(*WriteContext); ok { }else if context, ok := ioResult.(*ReadContext); ok { value := &CacheValue{} value.buf = append(value.buf, context.msgBuf[9:]...) t.cache.Set(context.globalOffset, value) //make sure the peer is still alive for _, v := range t.peers{ if v == context.peer{ context.peer.sendMessage(context.msgBuf) t.si.Uploaded += int64(context.length) } } }else{ panic("Unknown message") } case _ = <-rechokeChan: /* t.lastHeartBeat = time.Now() ratio := float64(0.0) if t.si.Downloaded > 0 { ratio = float64(t.si.Uploaded) / float64(t.si.Downloaded) } log.Println("Peers:", t.PeerCount(), "downloaded:", t.si.Downloaded, "uploaded:", t.si.Uploaded, "ratio", ratio) log.Println("good, total", t.goodPieces, t.totalPieces) */ if t.PeerCount() < cfg.TARGET_NUM_PEERS && t.goodPieces < t.totalPieces { if t.m.Info.Private != 1 && cfg.useDHT { go t.dht.PeersRequest(t.m.InfoHash, true) } if !cfg.trackerLessMode { if t.ti == nil || t.ti.Complete > 100 { t.fetchTrackerInfo("") } } } t.SchedulerChokeUnchoke() case _ = <-keepAliveChan: now := time.Now() for _, peer := range t.peers { if peer.lastReadTime.Second() != 0 && now.Sub(peer.lastReadTime) > 3*time.Minute { log.Println("Closing peer", peer.address, "because timed out.") t.ClosePeer(peer) continue } err2 := t.doCheckRequests(peer) if err2 != nil { if err2 != io.EOF { log.Println("Closing peer", peer.address, "because", err2) } t.ClosePeer(peer) continue } peer.keepAlive(now) } } } return }
func (t *TorrentSession) DoTorrent() { t.heartbeat = make(chan bool, 1) if t.flags.UseDeadlockDetector { go t.deadlockDetector() } log.Println("Fetching torrent.") heartbeatChan := time.Tick(1 * time.Second) keepAliveChan := time.Tick(60 * time.Second) var retrackerChan <-chan time.Time t.hintNewPeerChan = make(chan string) t.addPeerChan = make(chan *btConn) if !t.trackerLessMode { // Start out polling tracker every 20 seconds until we get a response. // Maybe be exponential backoff here? retrackerChan = time.Tick(20 * time.Second) t.trackerInfoChan = make(chan *TrackerResponse) t.trackerReportChan = make(chan ClientStatusReport) startTrackerClient(t.flags.Dial, t.M.Announce, t.M.AnnounceList, t.trackerInfoChan, t.trackerReportChan) } if t.si.UseDHT { t.dht.PeersRequest(t.M.InfoHash, true) } if !t.trackerLessMode && t.si.HaveTorrent { t.fetchTrackerInfo("started") } defer t.Shutdown() for { var peersRequestResults chan map[dht.InfoHash][]string peersRequestResults = nil if t.dht != nil { peersRequestResults = t.dht.PeersRequestResults } select { case <-t.chokePolicyHeartbeat: t.chokePeers() case hintNewPeer := <-t.hintNewPeerChan: t.hintNewPeerImp(hintNewPeer) case btconn := <-t.addPeerChan: t.addPeerImp(btconn) case <-retrackerChan: if !t.trackerLessMode { t.fetchTrackerInfo("") } case dhtInfoHashPeers := <-peersRequestResults: newPeerCount := 0 // key = infoHash. The torrent client currently only // supports one download at a time, so let's assume // it's the case. for _, peers := range dhtInfoHashPeers { for _, peer := range peers { peer = dht.DecodePeerAddress(peer) if t.mightAcceptPeer(peer) { newPeerCount++ if t.si.HaveTorrent { go t.connectToPeer(peer) } } } } // log.Println("Contacting", newPeerCount, "new peers (thanks DHT!)") case ti := <-t.trackerInfoChan: t.ti = ti log.Println("Torrent has", t.ti.Complete, "seeders and", t.ti.Incomplete, "leachers.") if !t.trackerLessMode { newPeerCount := 0 { peers := t.ti.Peers if len(peers) > 0 { const peerLen = 6 log.Println("Tracker gave us", len(peers)/peerLen, "peers") for i := 0; i < len(peers); i += peerLen { peer := nettools.BinaryToDottedPort(peers[i : i+peerLen]) if t.mightAcceptPeer(peer) { newPeerCount++ go t.connectToPeer(peer) } } } } { peers6 := t.ti.Peers6 if len(peers6) > 0 { const peerLen = 18 log.Println("Tracker gave us", len(peers6)/peerLen, "IPv6 peers") for i := 0; i < len(peers6); i += peerLen { peerEntry := peers6[i : i+peerLen] host := net.IP(peerEntry[0:16]) port := int((uint(peerEntry[16]) << 8) | uint(peerEntry[17])) peer := net.JoinHostPort(host.String(), strconv.Itoa(port)) if t.mightAcceptPeer(peer) { newPeerCount++ go t.connectToPeer(peer) } } } } log.Println("Contacting", newPeerCount, "new peers") } interval := t.ti.Interval minInterval := uint(120) maxInterval := uint(24 * 3600) if interval < minInterval { interval = minInterval } else if interval > maxInterval { interval = maxInterval } log.Println("..checking again in", interval, "seconds.") retrackerChan = time.Tick(time.Duration(interval) * time.Second) case pm := <-t.peerMessageChan: peer, message := pm.peer, pm.message peer.lastReadTime = time.Now() err2 := t.DoMessage(peer, message) if err2 != nil { if err2 != io.EOF { log.Println("Closing peer", peer.address, "because", err2) } t.ClosePeer(peer) } case <-heartbeatChan: if t.flags.UseDeadlockDetector { t.heartbeat <- true } ratio := float64(0.0) if t.si.Downloaded > 0 { ratio = float64(t.si.Uploaded) / float64(t.si.Downloaded) } log.Println("Peers:", len(t.peers), "downloaded:", t.si.Downloaded, "uploaded:", t.si.Uploaded, "ratio", ratio) log.Println("good, total", t.goodPieces, t.totalPieces) if t.goodPieces == t.totalPieces && ratio >= t.flags.SeedRatio { log.Println("Achieved target seed ratio", t.flags.SeedRatio) return } if len(t.peers) < TARGET_NUM_PEERS && (t.totalPieces == 0 || t.goodPieces < t.totalPieces) { if t.si.UseDHT { go t.dht.PeersRequest(t.M.InfoHash, true) } if !t.trackerLessMode { if t.ti == nil || t.ti.Complete > 100 { t.fetchTrackerInfo("") } } } case <-keepAliveChan: now := time.Now() for _, peer := range t.peers { if peer.lastReadTime.Second() != 0 && now.Sub(peer.lastReadTime) > 3*time.Minute { // log.Println("Closing peer", peer.address, "because timed out.") t.ClosePeer(peer) continue } err2 := t.doCheckRequests(peer) if err2 != nil { if err2 != io.EOF { log.Println("Closing peer", peer.address, "because", err2) } t.ClosePeer(peer) continue } peer.keepAlive(now) } case <-t.quit: log.Println("Quitting torrent session") return } } }
// Requires Internet access and can be flaky if the server or the internet is // slow. func TestDHTLarge(t *testing.T) { if testing.Short() { t.Skip("TestDHTLarge requires internet access and can be flaky. Skipping in short mode.") } defer stats(t) c := NewConfig() c.Port = 6060 // ... //c.SaveRoutingTable = false node, err := New(c) if err != nil { t.Fatalf("dht New: %v", err) } go node.Run() realDHTNodes := []string{ "1.a.magnets.im", "router.utorrent.com", } for _, addr := range realDHTNodes { ip, err := net.LookupHost(addr) if err != nil { t.Error(err) continue } node.AddNode(ip[0]+":6881", "") } node.AddNode("117.78.1.52:6891", "") node.AddNode("88.183.138.12:52804", "") // Test that we can reach at least one node. success := false var ( reachable int v expvar.Var ) for i := 0; i < 10; i++ { v = expvar.Get("totalNodesReached") reachable, err = strconv.Atoi(v.String()) if err != nil { t.Errorf("totalNodesReached conversion to int failed: %v", err) continue } if reachable > 0 { t.Logf("Contacted %d DHT nodes.", reachable) success = true break } time.Sleep(time.Second) } if !success { t.Fatal("No external DHT node could be contacted.") } // Test that we can find peers for a known torrent in a timely fashion. // // Torrent from: http://www.clearbits.net/torrents/244-time-management-for-anarchists-1 //infoHash := InfoHash("\xb4\x62\xc0\xa8\xbc\xef\x1c\xe5\xbb\x56\xb9\xfd\xb8\xcf\x37\xff\xd0\x2f\x5f\x59") infoHash := InfoHash("\xc0\x66\x42\x34\xb4\x4f\x25\xde\x6c\xc7\xb5\x36\xa7\x98\xc6\x5f\x85\x80\x79\xcb") //c0 66 42 34 b4 4f 25 de 6c c7 b5 36 a7 98 c6 5f 85 80 79 cb go node.PeersRequest(infoHash, true) timeout := make(chan bool, 1) go func() { time.Sleep(30 * time.Second) timeout <- true }() var infoHashPeers map[InfoHash][]string select { case infoHashPeers = <-node.PeersRequestResults: t.Logf("Found %d peers.", len(infoHashPeers[infoHash])) case <-timeout: t.Fatal("Could not find new peers: timed out") } for ih, peers := range infoHashPeers { if infoHash != ih { t.Fatal("Unexpected infohash returned") } if len(peers) == 0 { t.Fatal("Could not find new torrent peers.") } for _, peer := range peers { t.Logf("peer found: %v", nettools.BinaryToDottedPort(peer)) } } }
// Requires Internet access and can be flaky if the server or the internet is // slow. func TestDHTLarge(t *testing.T) { node := startDHTNode(t) realDHTNodes := []string{ "1.a.magnets.im", } for _, addr := range realDHTNodes { ip, err := net.LookupHost(addr) if err != nil { t.Error(err) continue } node.AddNode(ip[0] + ":6881") } // Test that we can reach at least one node. success := false var ( reachable int v expvar.Var err error ) for i := 0; i < 10; i++ { v = expvar.Get("totalReachableNodes") reachable, err = strconv.Atoi(v.String()) if err != nil { t.Errorf("totalReachableNodes conversion to int failed: %v", err) continue } if reachable > 0 { t.Logf("Contacted %d DHT nodes.", reachable) success = true break } time.Sleep(time.Second) } if !success { t.Fatal("No external DHT node could be contacted.") } // Test that we can find peers for a known torrent in a timely fashion. // // Torrent from: http://www.clearbits.net/torrents/244-time-management-for-anarchists-1 infoHash := InfoHash("\xb4\x62\xc0\xa8\xbc\xef\x1c\xe5\xbb\x56\xb9\xfd\xb8\xcf\x37\xff\xd0\x2f\x5f\x59") go node.PeersRequest(string(infoHash), true) timeout := make(chan bool, 1) go func() { time.Sleep(10 * time.Second) timeout <- true }() var infoHashPeers map[InfoHash][]string select { case infoHashPeers = <-node.PeersRequestResults: t.Logf("Found %d peers.", len(infoHashPeers[infoHash])) case <-timeout: t.Fatal("Could not find new peers: timed out") } for ih, peers := range infoHashPeers { if infoHash != ih { t.Fatal("Unexpected infohash returned") } if len(peers) == 0 { t.Fatal("Could not find new torrent peers.") } for _, peer := range peers { t.Logf("peer found: %v", nettools.BinaryToDottedPort(peer)) } } t.Logf("=== Stats ===") t.Logf("totalReachableNodes: %v", totalReachableNodes) t.Logf("totalDupes: %v", totalDupes) t.Logf("totalPeers: %v", totalPeers) t.Logf("totalSentGetPeers: %v", totalSentGetPeers) }