table.go 5.77 KB
Newer Older
1 2
// package kbucket implements a kademlia 'k-bucket' routing table.
package kbucket
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
3 4

import (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
5
	"container/list"
6
	"fmt"
7
	"sort"
Jeromy's avatar
Jeromy committed
8
	"sync"
9
	"time"
10

11
	peer "github.com/jbenet/go-ipfs/peer"
Jeromy's avatar
Jeromy committed
12
	u "github.com/jbenet/go-ipfs/util"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
13 14
)

15 16
var log = u.Logger("table")

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17 18 19
// RoutingTable defines the routing table.
type RoutingTable struct {

20 21 22
	// ID of the local peer
	local ID

Jeromy's avatar
Jeromy committed
23 24 25
	// Blanket lock, refine later for better performance
	tabLock sync.RWMutex

26 27 28
	// Maximum acceptable latency for peers in this cluster
	maxLatency time.Duration

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
29
	// kBuckets define all the fingers to other nodes.
Jeromy's avatar
Jeromy committed
30
	Buckets    []*Bucket
31 32 33
	bucketsize int
}

Chas Leichner's avatar
Chas Leichner committed
34 35
// NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance.
func NewRoutingTable(bucketsize int, localID ID, latency time.Duration) *RoutingTable {
36
	rt := new(RoutingTable)
37
	rt.Buckets = []*Bucket{newBucket()}
38
	rt.bucketsize = bucketsize
39
	rt.local = localID
40
	rt.maxLatency = latency
41
	return rt
42 43 44 45
}

// Update adds or moves the given peer to the front of its respective bucket
// If a peer gets removed from a bucket, it is returned
46
func (rt *RoutingTable) Update(p peer.Peer) peer.Peer {
Jeromy's avatar
Jeromy committed
47 48
	rt.tabLock.Lock()
	defer rt.tabLock.Unlock()
49
	peerID := ConvertPeerID(p.ID())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
50
	cpl := commonPrefixLen(peerID, rt.local)
51

52 53 54
	bucketID := cpl
	if bucketID >= len(rt.Buckets) {
		bucketID = len(rt.Buckets) - 1
55 56
	}

57
	bucket := rt.Buckets[bucketID]
58
	e := bucket.find(p.ID())
59 60
	if e == nil {
		// New peer, add to bucket
61 62 63 64
		if p.GetLatency() > rt.maxLatency {
			// Connection doesnt meet requirements, skip!
			return nil
		}
65
		bucket.pushFront(p)
66 67

		// Are we past the max bucket size?
68
		if bucket.len() > rt.bucketsize {
69 70
			// If this bucket is the rightmost bucket, and its full
			// we need to split it and create a new bucket
71
			if bucketID == len(rt.Buckets)-1 {
72
				return rt.nextBucket()
73 74
			} else {
				// If the bucket cant split kick out least active node
75
				return bucket.popBack()
76 77 78 79
			}
		}
		return nil
	}
80 81 82 83 84
	// If the peer is already in the table, move it to the front.
	// This signifies that it it "more active" and the less active nodes
	// Will as a result tend towards the back of the list
	bucket.moveToFront(e)
	return nil
85 86
}

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
func (rt *RoutingTable) nextBucket() peer.Peer {
	bucket := rt.Buckets[len(rt.Buckets)-1]
	newBucket := bucket.Split(len(rt.Buckets)-1, rt.local)
	rt.Buckets = append(rt.Buckets, newBucket)
	if newBucket.len() > rt.bucketsize {
		return rt.nextBucket()
	}

	// If all elements were on left side of split...
	if bucket.len() > rt.bucketsize {
		return bucket.popBack()
	}
	return nil
}

102 103
// A helper struct to sort peers by their distance to the local node
type peerDistance struct {
104
	p        peer.Peer
105 106
	distance ID
}
107 108

// peerSorterArr implements sort.Interface to sort peers by xor distance
109
type peerSorterArr []*peerDistance
Jeromy's avatar
Jeromy committed
110 111 112

func (p peerSorterArr) Len() int      { return len(p) }
func (p peerSorterArr) Swap(a, b int) { p[a], p[b] = p[b], p[a] }
113
func (p peerSorterArr) Less(a, b int) bool {
114
	return p[a].distance.less(p[b].distance)
115
}
Jeromy's avatar
Jeromy committed
116

117 118
//

Jeromy's avatar
Jeromy committed
119
func copyPeersFromList(target ID, peerArr peerSorterArr, peerList *list.List) peerSorterArr {
Jeromy's avatar
Jeromy committed
120
	for e := peerList.Front(); e != nil; e = e.Next() {
121 122
		p := e.Value.(peer.Peer)
		pID := ConvertPeerID(p.ID())
123
		pd := peerDistance{
Jeromy's avatar
Jeromy committed
124
			p:        p,
125
			distance: xor(target, pID),
126 127
		}
		peerArr = append(peerArr, &pd)
128
		if e == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129
			log.Debug("list element was nil")
Jeromy's avatar
Jeromy committed
130 131
			return peerArr
		}
132 133 134 135
	}
	return peerArr
}

Jeromy's avatar
Jeromy committed
136
// Find a specific peer by ID or return nil
137
func (rt *RoutingTable) Find(id peer.ID) peer.Peer {
Chas Leichner's avatar
Chas Leichner committed
138
	srch := rt.NearestPeers(ConvertPeerID(id), 1)
139
	if len(srch) == 0 || !srch[0].ID().Equal(id) {
Jeromy's avatar
Jeromy committed
140 141 142 143 144
		return nil
	}
	return srch[0]
}

145
// NearestPeer returns a single peer that is nearest to the given ID
146
func (rt *RoutingTable) NearestPeer(id ID) peer.Peer {
147
	peers := rt.NearestPeers(id, 1)
148 149 150
	if len(peers) > 0 {
		return peers[0]
	}
151

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
152
	log.Errorf("NearestPeer: Returning nil, table size = %d", rt.Size())
153
	return nil
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
154 155
}

156
// NearestPeers returns a list of the 'count' closest peers to the given ID
157
func (rt *RoutingTable) NearestPeers(id ID, count int) []peer.Peer {
Jeromy's avatar
Jeromy committed
158 159
	rt.tabLock.RLock()
	defer rt.tabLock.RUnlock()
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
160
	cpl := commonPrefixLen(id, rt.local)
161 162 163 164

	// Get bucket at cpl index or last bucket
	var bucket *Bucket
	if cpl >= len(rt.Buckets) {
165
		cpl = len(rt.Buckets) - 1
166
	}
167
	bucket = rt.Buckets[cpl]
168

169
	var peerArr peerSorterArr
170
	if bucket.len() == 0 {
171 172 173
		// In the case of an unusual split, one bucket may be empty.
		// if this happens, search both surrounding buckets for nearest peer
		if cpl > 0 {
174
			plist := rt.Buckets[cpl-1].list
Jeromy's avatar
Jeromy committed
175
			peerArr = copyPeersFromList(id, peerArr, plist)
176
		}
177

Jeromy's avatar
Jeromy committed
178
		if cpl < len(rt.Buckets)-1 {
179
			plist := rt.Buckets[cpl+1].list
Jeromy's avatar
Jeromy committed
180
			peerArr = copyPeersFromList(id, peerArr, plist)
181
		}
182
	} else {
183
		peerArr = copyPeersFromList(id, peerArr, bucket.list)
184 185
	}

186
	// Sort by distance to local peer
187 188
	sort.Sort(peerArr)

189
	var out []peer.Peer
190 191 192 193 194
	for i := 0; i < count && i < peerArr.Len(); i++ {
		out = append(out, peerArr[i].p)
	}

	return out
195
}
Jeromy's avatar
Jeromy committed
196

197
// Size returns the total number of peers in the routing table
Jeromy's avatar
Jeromy committed
198 199
func (rt *RoutingTable) Size() int {
	var tot int
Jeromy's avatar
Jeromy committed
200
	for _, buck := range rt.Buckets {
201
		tot += buck.len()
Jeromy's avatar
Jeromy committed
202 203 204
	}
	return tot
}
205

Chas Leichner's avatar
Chas Leichner committed
206
// ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table.
207
// NOTE: This is potentially unsafe... use at your own risk
208 209
func (rt *RoutingTable) ListPeers() []peer.Peer {
	var peers []peer.Peer
Jeromy's avatar
Jeromy committed
210
	for _, buck := range rt.Buckets {
211
		for e := buck.getIter(); e != nil; e = e.Next() {
212
			peers = append(peers, e.Value.(peer.Peer))
213 214 215 216
		}
	}
	return peers
}
217

Chas Leichner's avatar
Chas Leichner committed
218 219
// Print prints a descriptive statement about the provided RoutingTable
func (rt *RoutingTable) Print() {
220 221
	fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency)
	rt.tabLock.RLock()
Chas Leichner's avatar
Chas Leichner committed
222
	peers := rt.ListPeers()
223
	for i, p := range peers {
224
		fmt.Printf("%d) %s %s\n", i, p.ID().Pretty(), p.GetLatency().String())
225 226
	}
}