1 // Package DHT implements a DHT for use with the BitTorrent protocol,
2 // described in BEP 5: http://www.bittorrent.org/beps/bep_0005.html.
4 // Standard use involves creating a NewServer, and calling Announce on it with
5 // the details of your local torrent client and infohash of interest.
24 "github.com/anacrolix/missinggo"
25 "github.com/anacrolix/sync"
26 "github.com/tylertreat/BoomFilters"
28 "github.com/anacrolix/torrent/bencode"
29 "github.com/anacrolix/torrent/iplist"
30 "github.com/anacrolix/torrent/logonce"
31 "github.com/anacrolix/torrent/util"
36 queryResendEvery = 5 * time.Second
39 // Uniquely identifies a transaction to us.
40 type transactionKey struct {
41 RemoteAddr string // host:port
42 T string // The KRPC transaction ID.
48 transactions map[transactionKey]*Transaction
49 transactionIDInt uint64
50 nodes map[string]*node // Keyed by dHTAddr.String().
53 ipBlockList iplist.Ranger
54 badNodes *boom.BloomFilter
56 numConfirmedAnnounces int
57 bootstrapNodes []string
61 type ServerConfig struct {
62 Addr string // Listen address. Used if Conn is nil.
64 // Don't respond to queries from other nodes.
66 // DHT Bootstrap nodes
67 BootstrapNodes []string
68 // Disable the DHT security extension:
69 // http://www.libtorrent.org/dht_sec.html.
71 // Initial IP blocklist to use. Applied before serving and bootstrapping
73 IPBlocklist iplist.Ranger
74 // Used to secure the server's ID. Defaults to the Conn's LocalAddr().
78 type ServerStats struct {
79 // Count of nodes in the node table that responded to our last query or
80 // haven't yet been queried.
82 // Count of nodes in the node table.
84 // Transactions awaiting a response.
85 OutstandingTransactions int
86 // Individual announce_peer requests that got a success response.
87 ConfirmedAnnounces int
88 // Nodes that have been blocked.
92 // Returns statistics for the server.
93 func (s *Server) Stats() (ss ServerStats) {
96 for _, n := range s.nodes {
97 if n.DefinitelyGood() {
101 ss.Nodes = len(s.nodes)
102 ss.OutstandingTransactions = len(s.transactions)
103 ss.ConfirmedAnnounces = s.numConfirmedAnnounces
104 ss.BadNodes = s.badNodes.Count()
108 // Returns the listen address for the server. Packets arriving to this address
109 // are processed by the server (unless aliens are involved).
110 func (s *Server) Addr() net.Addr {
111 return s.socket.LocalAddr()
114 func makeSocket(addr string) (socket *net.UDPConn, err error) {
115 addr_, err := net.ResolveUDPAddr("", addr)
119 socket, err = net.ListenUDP("udp", addr_)
123 // Create a new DHT server.
124 func NewServer(c *ServerConfig) (s *Server, err error) {
130 ipBlockList: c.IPBlocklist,
131 badNodes: boom.NewBloomFilter(1000, 0.1),
136 s.socket, err = makeSocket(c.Addr)
141 s.bootstrapNodes = c.BootstrapNodes
163 log.Printf("error bootstrapping DHT: %s", err)
170 // Returns a description of the Server. Python repr-style.
171 func (s *Server) String() string {
172 return fmt.Sprintf("dht server on %s", s.socket.LocalAddr())
180 func (nid *nodeID) IsUnset() bool {
184 func nodeIDFromString(s string) (ret nodeID) {
188 ret.i.SetBytes([]byte(s))
193 func (nid0 *nodeID) Distance(nid1 *nodeID) (ret big.Int) {
194 if nid0.IsUnset() != nid1.IsUnset() {
198 ret.Xor(&nid0.i, &nid1.i)
202 func (nid *nodeID) ByteString() string {
205 copy(buf[20-len(b):], b)
206 return string(buf[:])
214 lastGotQuery time.Time
215 lastGotResponse time.Time
216 lastSentQuery time.Time
219 func (n *node) IsSecure() bool {
223 return NodeIdSecure(n.id.ByteString(), n.addr.IP())
226 func (n *node) idString() string {
227 return n.id.ByteString()
230 func (n *node) SetIDFromBytes(b []byte) {
238 func (n *node) SetIDFromString(s string) {
239 n.SetIDFromBytes([]byte(s))
242 func (n *node) IDNotSet() bool {
243 return n.id.i.Int64() == 0
246 func (n *node) NodeInfo() (ret NodeInfo) {
248 if n := copy(ret.ID[:], n.idString()); n != 20 {
254 func (n *node) DefinitelyGood() bool {
255 if len(n.idString()) != 20 {
258 // No reason to think ill of them if they've never been queried.
259 if n.lastSentQuery.IsZero() {
262 // They answered our last query.
263 if n.lastSentQuery.Before(n.lastGotResponse) {
269 // A wrapper around the unmarshalled KRPC dict that constitutes messages in
270 // the DHT. There are various helpers for extracting common data from the
271 // message. In normal use, Msg is abstracted away for you, but it can be of
273 type Msg map[string]interface{}
275 var _ fmt.Stringer = Msg{}
277 func (m Msg) String() string {
278 return fmt.Sprintf("%#v", m)
281 func (m Msg) T() (t string) {
290 func (m Msg) Args() map[string]interface{} {
294 return m["a"].(map[string]interface{})
297 func (m Msg) SenderID() string {
301 switch m["y"].(string) {
303 return m.Args()["id"].(string)
305 return m["r"].(map[string]interface{})["id"].(string)
310 // Suggested nodes in a response.
311 func (m Msg) Nodes() (nodes []NodeInfo) {
316 return m["r"].(map[string]interface{})["nodes"].(string)
321 for i := 0; i < len(b); i += 26 {
323 err := n.UnmarshalCompact([]byte(b[i : i+26]))
327 nodes = append(nodes, n)
332 type KRPCError struct {
337 func (me KRPCError) Error() string {
338 return fmt.Sprintf("KRPC error %d: %s", me.Code, me.Msg)
341 var _ error = KRPCError{}
343 func (m Msg) Error() (ret *KRPCError) {
348 switch e := m["e"].(type) {
350 ret.Code = int(e[0].(int64))
351 ret.Msg = e[1].(string)
355 logonce.Stderr.Printf(`KRPC error "e" value has unexpected type: %T`, e)
360 // Returns the token given in response to a get_peers request for future
361 // announce_peer requests to that node.
362 func (m Msg) AnnounceToken() (token string, ok bool) {
363 defer func() { recover() }()
364 token, ok = m["r"].(map[string]interface{})["token"].(string)
368 type Transaction struct {
373 onResponse func(Msg) // Called with the server locked.
380 userOnResponse func(Msg)
383 // Set a function to be called with the response.
384 func (t *Transaction) SetResponseHandler(f func(Msg)) {
388 t.tryHandleResponse()
391 func (t *Transaction) tryHandleResponse() {
392 if t.userOnResponse == nil {
396 case r := <-t.response:
398 // Shouldn't be called more than once.
399 t.userOnResponse = nil
404 func (t *Transaction) key() transactionKey {
405 return transactionKey{
406 t.remoteAddr.String(),
411 func jitterDuration(average time.Duration, plusMinus time.Duration) time.Duration {
412 return average - plusMinus/2 + time.Duration(rand.Int63n(int64(plusMinus)))
415 func (t *Transaction) startTimer() {
416 t.timer = time.AfterFunc(jitterDuration(queryResendEvery, time.Second), t.timerCallback)
419 func (t *Transaction) timerCallback() {
433 if t.timer.Reset(jitterDuration(queryResendEvery, time.Second)) {
434 panic("timer should have fired to get here")
438 func (t *Transaction) sendQuery() error {
439 err := t.s.writeToNode(t.queryPacket, t.remoteAddr)
443 t.lastSend = time.Now()
447 func (t *Transaction) timeout() {
450 defer t.s.mu.Unlock()
451 t.s.nodeTimedOut(t.remoteAddr)
456 func (t *Transaction) close() {
462 t.tryHandleResponse()
467 defer t.s.mu.Unlock()
468 t.s.deleteTransaction(t)
472 func (t *Transaction) closing() bool {
481 // Abandon the transaction.
482 func (t *Transaction) Close() {
488 func (t *Transaction) handleResponse(m Msg) {
496 if t.onResponse != nil {
503 case t.response <- m:
505 panic("blocked handling response")
508 t.tryHandleResponse()
511 func maskForIP(ip net.IP) []byte {
513 case ip.To4() != nil:
514 return []byte{0x03, 0x0f, 0x3f, 0xff}
516 return []byte{0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}
520 // Generate the CRC used to make or validate secure node ID.
521 func crcIP(ip net.IP, rand uint8) uint32 {
522 if ip4 := ip.To4(); ip4 != nil {
525 // Copy IP so we can make changes. Go sux at this.
526 ip = append(make(net.IP, 0, len(ip)), ip...)
527 mask := maskForIP(ip)
528 for i := range mask {
533 return crc32.Checksum(ip[:len(mask)], crc32.MakeTable(crc32.Castagnoli))
536 // Makes a node ID secure, in-place. The ID is 20 raw bytes.
537 // http://www.libtorrent.org/dht_sec.html
538 func SecureNodeId(id []byte, ip net.IP) {
539 crc := crcIP(ip, id[19])
540 id[0] = byte(crc >> 24 & 0xff)
541 id[1] = byte(crc >> 16 & 0xff)
542 id[2] = byte(crc>>8&0xf8) | id[2]&7
545 // Returns whether the node ID is considered secure. The id is the 20 raw
546 // bytes. http://www.libtorrent.org/dht_sec.html
547 func NodeIdSecure(id string, ip net.IP) bool {
549 panic(fmt.Sprintf("%q", id))
551 if ip4 := ip.To4(); ip4 != nil {
554 crc := crcIP(ip, id[19])
555 if id[0] != byte(crc>>24&0xff) {
558 if id[1] != byte(crc>>16&0xff) {
561 if id[2]&0xf8 != byte(crc>>8&0xf8) {
567 func (s *Server) setDefaults() (err error) {
570 h := crypto.SHA1.New()
571 ss, err := os.Hostname()
575 ss += s.socket.LocalAddr().String()
577 if b := h.Sum(id[:0:20]); len(b) != 20 {
583 publicIP := func() net.IP {
584 if s.config.PublicIP != nil {
585 return s.config.PublicIP
587 return missinggo.AddrIP(s.socket.LocalAddr())
590 SecureNodeId(id[:], publicIP)
593 s.nodes = make(map[string]*node, maxNodes)
597 // Packets to and from any address matching a range in the list are dropped.
598 func (s *Server) SetIPBlockList(list iplist.Ranger) {
604 func (s *Server) IPBlocklist() iplist.Ranger {
608 func (s *Server) init() (err error) {
609 err = s.setDefaults()
613 s.closed = make(chan struct{})
614 s.transactions = make(map[transactionKey]*Transaction)
618 func (s *Server) processPacket(b []byte, addr dHTAddr) {
619 if len(b) < 2 || b[0] != 'd' || b[len(b)-1] != 'e' {
620 // KRPC messages are bencoded dicts.
621 readNotKRPCDict.Add(1)
625 err := bencode.Unmarshal(b, &d)
627 readUnmarshalError.Add(1)
629 if se, ok := err.(*bencode.SyntaxError); ok {
630 // The message was truncated.
631 if int(se.Offset) == len(b) {
634 // Some messages seem to drop to nul chars abrubtly.
635 if int(se.Offset) < len(b) && b[se.Offset] == 0 {
638 // The message isn't bencode from the first.
643 if missinggo.CryHeard() {
644 log.Printf("%s: received bad krpc message from %s: %s: %+q", s, addr, err, b)
653 s.handleQuery(addr, d)
656 t := s.findResponseTransaction(d.T(), addr)
658 //log.Printf("unexpected message: %#v", d)
661 node := s.getNode(addr, d.SenderID())
662 node.lastGotResponse = time.Now()
663 // TODO: Update node ID as this is an authoritative packet.
664 go t.handleResponse(d)
665 s.deleteTransaction(t)
668 func (s *Server) serve() error {
671 n, addr, err := s.socket.ReadFrom(b[:])
677 logonce.Stderr.Printf("received dht packet exceeds buffer size")
681 blocked := s.ipBlocked(missinggo.AddrIP(addr))
687 s.processPacket(b[:n], newDHTAddr(addr))
691 func (s *Server) ipBlocked(ip net.IP) bool {
692 if s.ipBlockList == nil {
695 return s.ipBlockList.Lookup(ip) != nil
698 // Adds directly to the node table.
699 func (s *Server) AddNode(ni NodeInfo) {
703 s.nodes = make(map[string]*node)
705 s.getNode(ni.Addr, string(ni.ID[:]))
708 func (s *Server) nodeByID(id string) *node {
709 for _, node := range s.nodes {
710 if node.idString() == id {
717 func (s *Server) handleQuery(source dHTAddr, m Msg) {
718 node := s.getNode(source, m.SenderID())
719 node.lastGotQuery = time.Now()
721 if s.config.Passive {
730 s.reply(source, m["t"].(string), nil)
731 case "get_peers": // TODO: Extract common behaviour with find_node.
732 targetID := args["info_hash"].(string)
733 if len(targetID) != 20 {
736 var rNodes []NodeInfo
737 // TODO: Reply with "values" list if we have peers instead.
738 for _, node := range s.closestGoodNodes(8, targetID) {
739 rNodes = append(rNodes, node.NodeInfo())
741 nodesBytes := make([]byte, CompactNodeInfoLen*len(rNodes))
742 for i, ni := range rNodes {
743 err := ni.PutCompact(nodesBytes[i*CompactNodeInfoLen : (i+1)*CompactNodeInfoLen])
748 s.reply(source, m["t"].(string), map[string]interface{}{
749 "nodes": string(nodesBytes),
752 case "find_node": // TODO: Extract common behaviour with get_peers.
753 targetID := args["target"].(string)
754 if len(targetID) != 20 {
755 log.Printf("bad DHT query: %v", m)
758 var rNodes []NodeInfo
759 if node := s.nodeByID(targetID); node != nil {
760 rNodes = append(rNodes, node.NodeInfo())
762 for _, node := range s.closestGoodNodes(8, targetID) {
763 rNodes = append(rNodes, node.NodeInfo())
766 nodesBytes := make([]byte, CompactNodeInfoLen*len(rNodes))
767 for i, ni := range rNodes {
768 // TODO: Put IPv6 nodes into the correct dict element.
769 if ni.Addr.UDPAddr().IP.To4() == nil {
772 err := ni.PutCompact(nodesBytes[i*CompactNodeInfoLen : (i+1)*CompactNodeInfoLen])
774 log.Printf("error compacting %#v: %s", ni, err)
778 s.reply(source, m["t"].(string), map[string]interface{}{
779 "nodes": string(nodesBytes),
781 case "announce_peer":
782 // TODO(anacrolix): Implement this lolz.
785 // TODO(anacrolix): Or reject, I don't think I want this.
787 log.Printf("%s: not handling received query: q=%s", s, m["q"])
792 func (s *Server) reply(addr dHTAddr, t string, r map[string]interface{}) {
794 r = make(map[string]interface{}, 1)
797 m := map[string]interface{}{
802 b, err := bencode.Marshal(m)
806 err = s.writeToNode(b, addr)
808 log.Printf("error replying to %s: %s", addr, err)
812 // Returns a node struct for the addr. It is taken from the table or created
813 // and possibly added if required and meets validity constraints.
814 func (s *Server) getNode(addr dHTAddr, id string) (n *node) {
815 addrStr := addr.String()
819 n.SetIDFromString(id)
827 n.SetIDFromString(id)
829 if len(s.nodes) >= maxNodes {
832 if !s.config.NoSecurity && !n.IsSecure() {
835 if s.badNodes.Test([]byte(addrStr)) {
842 func (s *Server) nodeTimedOut(addr dHTAddr) {
843 node, ok := s.nodes[addr.String()]
847 if node.DefinitelyGood() {
850 if len(s.nodes) < maxNodes {
853 delete(s.nodes, addr.String())
856 func (s *Server) writeToNode(b []byte, node dHTAddr) (err error) {
857 if list := s.ipBlockList; list != nil {
858 if r := list.Lookup(missinggo.AddrIP(node.UDPAddr())); r != nil {
859 err = fmt.Errorf("write to %s blocked: %s", node, r.Description)
863 n, err := s.socket.WriteTo(b, node.UDPAddr())
865 err = fmt.Errorf("error writing %d bytes to %s: %#v", len(b), node, err)
869 err = io.ErrShortWrite
875 func (s *Server) findResponseTransaction(transactionID string, sourceNode dHTAddr) *Transaction {
876 return s.transactions[transactionKey{
881 func (s *Server) nextTransactionID() string {
882 var b [binary.MaxVarintLen64]byte
883 n := binary.PutUvarint(b[:], s.transactionIDInt)
888 func (s *Server) deleteTransaction(t *Transaction) {
889 delete(s.transactions, t.key())
892 func (s *Server) addTransaction(t *Transaction) {
893 if _, ok := s.transactions[t.key()]; ok {
894 panic("transaction not unique")
896 s.transactions[t.key()] = t
899 // Returns the 20-byte server ID. This is the ID used to communicate with the
901 func (s *Server) ID() string {
908 func (s *Server) query(node dHTAddr, q string, a map[string]interface{}, onResponse func(Msg)) (t *Transaction, err error) {
909 tid := s.nextTransactionID()
911 a = make(map[string]interface{}, 1)
914 d := map[string]interface{}{
920 // BEP 43. Outgoing queries from uncontactiable nodes should contain
921 // "ro":1 in the top level dictionary.
922 if s.config.Passive {
925 b, err := bencode.Marshal(d)
932 response: make(chan Msg, 1),
933 done: make(chan struct{}),
936 onResponse: onResponse,
942 s.getNode(node, "").lastSentQuery = time.Now()
948 // The size in bytes of a NodeInfo in its compact binary representation.
949 const CompactNodeInfoLen = 26
951 type NodeInfo struct {
956 // Writes the node info to its compact binary representation in b. See
957 // CompactNodeInfoLen.
958 func (ni *NodeInfo) PutCompact(b []byte) error {
959 if n := copy(b[:], ni.ID[:]); n != 20 {
962 ip := missinggo.AddrIP(ni.Addr).To4()
964 return errors.New("expected ipv4 address")
966 if n := copy(b[20:], ip); n != 4 {
969 binary.BigEndian.PutUint16(b[24:], uint16(missinggo.AddrPort(ni.Addr)))
973 func (cni *NodeInfo) UnmarshalCompact(b []byte) error {
975 return errors.New("expected 26 bytes")
977 missinggo.CopyExact(cni.ID[:], b[:20])
978 cni.Addr = newDHTAddr(&net.UDPAddr{
979 IP: net.IPv4(b[20], b[21], b[22], b[23]),
980 Port: int(binary.BigEndian.Uint16(b[24:26])),
985 // Sends a ping query to the address given.
986 func (s *Server) Ping(node *net.UDPAddr) (*Transaction, error) {
989 return s.query(newDHTAddr(node), "ping", nil, nil)
992 func (s *Server) announcePeer(node dHTAddr, infoHash string, port int, token string, impliedPort bool) (err error) {
993 if port == 0 && !impliedPort {
994 return errors.New("nothing to announce")
996 _, err = s.query(node, "announce_peer", map[string]interface{}{
997 "implied_port": func() int {
1004 "info_hash": infoHash,
1008 if err := m.Error(); err != nil {
1009 announceErrors.Add(1)
1011 // logonce.Stderr.Printf("announce_peer response: %s", err)
1014 s.numConfirmedAnnounces++
1019 // Add response nodes to node table.
1020 func (s *Server) liftNodes(d Msg) {
1024 for _, cni := range d.Nodes() {
1025 if missinggo.AddrPort(cni.Addr) == 0 {
1026 // TODO: Why would people even do this?
1029 if s.ipBlocked(missinggo.AddrIP(cni.Addr)) {
1032 n := s.getNode(cni.Addr, string(cni.ID[:]))
1033 n.SetIDFromBytes(cni.ID[:])
1037 // Sends a find_node query to addr. targetID is the node we're looking for.
1038 func (s *Server) findNode(addr dHTAddr, targetID string) (t *Transaction, err error) {
1039 t, err = s.query(addr, "find_node", map[string]interface{}{"target": targetID}, func(d Msg) {
1040 // Scrape peers from the response to put in the server's table before
1041 // handing the response back to the caller.
1055 func (me *Peer) String() string {
1056 return net.JoinHostPort(me.IP.String(), strconv.FormatInt(int64(me.Port), 10))
1059 // In a get_peers response, the addresses of torrent clients involved with the
1060 // queried info-hash.
1061 func (m Msg) Values() (vs []Peer) {
1062 v := func() interface{} {
1066 return m["r"].(map[string]interface{})["values"]
1071 vl, ok := v.([]interface{})
1073 if missinggo.CryHeard() {
1074 log.Printf(`unexpected krpc "values" field: %#v`, v)
1078 vs = make([]Peer, 0, len(vl))
1079 for _, i := range vl {
1084 // Because it's a list of strings, we can let the length of the string
1085 // determine the IP version of the compact peer.
1086 var cp util.CompactPeer
1087 err := cp.UnmarshalBinary([]byte(s))
1089 log.Printf("error decoding values list element: %s", err)
1092 vs = append(vs, Peer{cp.IP[:], int(cp.Port)})
1097 func (s *Server) getPeers(addr dHTAddr, infoHash string) (t *Transaction, err error) {
1098 if len(infoHash) != 20 {
1099 err = fmt.Errorf("infohash has bad length")
1102 t, err = s.query(addr, "get_peers", map[string]interface{}{"info_hash": infoHash}, func(m Msg) {
1104 at, ok := m.AnnounceToken()
1106 s.getNode(addr, m.SenderID()).announceToken = at
1112 func bootstrapAddrs(nodeAddrs []string) (addrs []*net.UDPAddr, err error) {
1113 bootstrapNodes := nodeAddrs
1114 if len(bootstrapNodes) == 0 {
1115 bootstrapNodes = []string{
1116 "router.utorrent.com:6881",
1117 "router.bittorrent.com:6881",
1120 for _, addrStr := range bootstrapNodes {
1121 udpAddr, err := net.ResolveUDPAddr("udp4", addrStr)
1125 addrs = append(addrs, udpAddr)
1127 if len(addrs) == 0 {
1128 err = errors.New("nothing resolved")
1133 // Adds bootstrap nodes directly to table, if there's room. Node ID security
1134 // is bypassed, but the IP blocklist is not.
1135 func (s *Server) addRootNodes() error {
1136 addrs, err := bootstrapAddrs(s.bootstrapNodes)
1140 for _, addr := range addrs {
1141 if len(s.nodes) >= maxNodes {
1144 if s.nodes[addr.String()] != nil {
1147 if s.ipBlocked(addr.IP) {
1148 log.Printf("dht root node is in the blocklist: %s", addr.IP)
1151 s.nodes[addr.String()] = &node{
1152 addr: newDHTAddr(addr),
1158 // Populates the node table.
1159 func (s *Server) bootstrap() (err error) {
1162 if len(s.nodes) == 0 {
1163 err = s.addRootNodes()
1169 var outstanding sync.WaitGroup
1170 for _, node := range s.nodes {
1172 t, err = s.findNode(node.addr, s.id)
1174 err = fmt.Errorf("error sending find_node: %s", err)
1178 t.SetResponseHandler(func(Msg) {
1182 noOutstanding := make(chan struct{})
1185 close(noOutstanding)
1192 case <-time.After(15 * time.Second):
1193 case <-noOutstanding:
1196 // log.Printf("now have %d nodes", len(s.nodes))
1197 if s.numGoodNodes() >= 160 {
1204 func (s *Server) numGoodNodes() (num int) {
1205 for _, n := range s.nodes {
1206 if n.DefinitelyGood() {
1213 // Returns how many nodes are in the node table.
1214 func (s *Server) NumNodes() int {
1220 // Exports the current node table.
1221 func (s *Server) Nodes() (nis []NodeInfo) {
1224 for _, node := range s.nodes {
1225 // if !node.Good() {
1231 if n := copy(ni.ID[:], node.idString()); n != 20 && n != 0 {
1234 nis = append(nis, ni)
1239 // Stops the server network activity. This is all that's required to clean-up a Server.
1240 func (s *Server) Close() {
1251 var maxDistance big.Int
1255 maxDistance.SetBit(&zero, 160, 1)
1258 func (s *Server) closestGoodNodes(k int, targetID string) []*node {
1259 return s.closestNodes(k, nodeIDFromString(targetID), func(n *node) bool { return n.DefinitelyGood() })
1262 func (s *Server) closestNodes(k int, target nodeID, filter func(*node) bool) []*node {
1263 sel := newKClosestNodesSelector(k, target)
1264 idNodes := make(map[string]*node, len(s.nodes))
1265 for _, node := range s.nodes {
1270 idNodes[node.idString()] = node
1273 ret := make([]*node, 0, len(ids))
1274 for _, id := range ids {
1275 ret = append(ret, idNodes[id.ByteString()])
1280 func (me *Server) badNode(addr dHTAddr) {
1281 me.badNodes.Add([]byte(addr.String()))
1282 delete(me.nodes, addr.String())