aboutsummaryrefslogtreecommitdiff
path: root/src/gnunet/service/dht/routingtable.go
diff options
context:
space:
mode:
authorBernd Fix <brf@hoi-polloi.org>2022-06-01 20:59:52 +0200
committerBernd Fix <brf@hoi-polloi.org>2022-06-01 20:59:52 +0200
commit913c80f317270b41f339048afa5d63278eecf8f4 (patch)
tree7ef1db049af2f4b009d97ab7240bb4744be9dfb6 /src/gnunet/service/dht/routingtable.go
parent205cad60026bf0af1cd2712a8faa4bce08eafb1d (diff)
downloadgnunet-go-913c80f317270b41f339048afa5d63278eecf8f4.tar.gz
gnunet-go-913c80f317270b41f339048afa5d63278eecf8f4.zip
Reworked gnunet/transport and gnunet/service.
Diffstat (limited to 'src/gnunet/service/dht/routingtable.go')
-rw-r--r--src/gnunet/service/dht/routingtable.go305
1 files changed, 305 insertions, 0 deletions
diff --git a/src/gnunet/service/dht/routingtable.go b/src/gnunet/service/dht/routingtable.go
new file mode 100644
index 0000000..895a1b2
--- /dev/null
+++ b/src/gnunet/service/dht/routingtable.go
@@ -0,0 +1,305 @@
1// This file is part of gnunet-go, a GNUnet-implementation in Golang.
2// Copyright (C) 2019-2022 Bernd Fix >Y<
3//
4// gnunet-go is free software: you can redistribute it and/or modify it
5// under the terms of the GNU Affero General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// gnunet-go is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17// SPDX-License-Identifier: AGPL3.0-or-later
18
19package dht
20
21import (
22 "bytes"
23 "crypto/sha512"
24 "encoding/hex"
25 "gnunet/util"
26 "math/rand"
27 "sync"
28
29 "github.com/bfix/gospel/math"
30)
31
32var (
33 // routing table hash function: defines number of
34 // buckets and size of peer addresses
35 rtHash = sha512.New
36)
37
38// Routing table contants (adjust with changing hash function)
39const (
40 numBuckets = 512 // number of bits of hash function result
41 numK = 20 // number of entries per k-bucket
42 sizeAddr = 64 // size of peer address in bytes
43)
44
45//======================================================================
46//======================================================================
47
48// PeerAddress is the identifier for a peer in the DHT network.
49// It is the SHA-512 hash of the PeerID (public Ed25519 key).
50type PeerAddress struct {
51 addr [sizeAddr]byte
52}
53
54// NewPeerAddress returns the DHT address of a peer.
55func NewPeerAddress(peer *util.PeerID) *PeerAddress {
56 r := new(PeerAddress)
57 h := rtHash()
58 h.Write(peer.Key)
59 copy(r.addr[:], h.Sum(nil))
60 return r
61}
62
63// String returns a human-readble representation of an address.
64func (addr *PeerAddress) String() string {
65 return hex.EncodeToString(addr.addr[:])
66}
67
68// Equals returns true if two peer addresses are the same.
69func (addr *PeerAddress) Equals(p *PeerAddress) bool {
70 return bytes.Equal(addr.addr[:], p.addr[:])
71}
72
73// Distance between two addresses: returns a distance value and a
74// bucket index (smaller index = less distant).
75func (addr *PeerAddress) Distance(p *PeerAddress) (*math.Int, int) {
76 var d PeerAddress
77 for i := range d.addr {
78 d.addr[i] = addr.addr[i] ^ p.addr[i]
79 }
80 r := math.NewIntFromBytes(d.addr[:])
81 return r, numBuckets - r.BitLen()
82}
83
84//======================================================================
85// Routing table implementation
86//======================================================================
87
88// RoutingTable holds the (local) routing table for a node.
89// The index of of an address is the number of bits in the
90// distance to the reference address, so smaller index means
91// "nearer" to the reference address.
92type RoutingTable struct {
93 ref *PeerAddress // reference address for distance
94 buckets []*Bucket // list of buckets
95 list map[*PeerAddress]bool // keep list of peers
96 rwlock sync.RWMutex // lock for write operations
97 l2nse float64 // log2 of estimated network size
98}
99
100// NewRoutingTable creates a new routing table for the reference address.
101func NewRoutingTable(ref *PeerAddress) *RoutingTable {
102 rt := new(RoutingTable)
103 rt.ref = ref
104 rt.list = make(map[*PeerAddress]bool)
105 rt.buckets = make([]*Bucket, numBuckets)
106 for i := range rt.buckets {
107 rt.buckets[i] = NewBucket(numK)
108 }
109 return rt
110}
111
112// Add new peer address to routing table.
113// Returns true if the entry was added, false otherwise.
114func (rt *RoutingTable) Add(p *PeerAddress, connected bool) bool {
115 // ensure one write and no readers
116 rt.rwlock.Lock()
117 defer rt.rwlock.Unlock()
118
119 // compute distance (bucket index) and insert address.
120 _, idx := p.Distance(rt.ref)
121 if rt.buckets[idx].Add(p, connected) {
122 rt.list[p] = true
123 return true
124 }
125 // Full bucket: we did not add the address to the routing table.
126 return false
127}
128
129// Remove peer address from routing table.
130// Returns true if the entry was removed, false otherwise.
131func (rt *RoutingTable) Remove(p *PeerAddress) bool {
132 // ensure one write and no readers
133 rt.rwlock.Lock()
134 defer rt.rwlock.Unlock()
135
136 // compute distance (bucket index) and remove entry from bucket
137 _, idx := p.Distance(rt.ref)
138 if rt.buckets[idx].Remove(p) {
139 delete(rt.list, p)
140 return true
141 }
142 return false
143}
144
145//----------------------------------------------------------------------
146// routing functions
147//----------------------------------------------------------------------
148
149// SelectClosestPeer for a given peer address and bloomfilter.
150func (rt *RoutingTable) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n *PeerAddress) {
151 // no writer allowed
152 rt.rwlock.RLock()
153 defer rt.rwlock.RUnlock()
154
155 // find closest address
156 var dist *math.Int
157 for _, b := range rt.buckets {
158 if k, d := b.SelectClosestPeer(p, bf); n == nil || (d != nil && d.Cmp(dist) < 0) {
159 dist = d
160 n = k
161 }
162 }
163 return
164}
165
166// SelectRandomPeer returns a random address from table (that is not
167// included in the bloomfilter)
168func (rt *RoutingTable) SelectRandomPeer(bf *PeerBloomFilter) *PeerAddress {
169 // no writer allowed
170 rt.rwlock.RLock()
171 defer rt.rwlock.RUnlock()
172
173 // select random entry from list
174 if size := len(rt.list); size > 0 {
175 idx := rand.Intn(size)
176 for k := range rt.list {
177 if idx == 0 {
178 return k
179 }
180 idx--
181 }
182 }
183 return nil
184}
185
186// SelectPeer selects a neighbor depending on the number of hops parameter.
187// If hops < NSE this function MUST return SelectRandomPeer() and
188// SelectClosestpeer() otherwise.
189func (rt *RoutingTable) SelectPeer(p *PeerAddress, hops int, bf *PeerBloomFilter) *PeerAddress {
190 if float64(hops) < rt.l2nse {
191 return rt.SelectRandomPeer(bf)
192 }
193 return rt.SelectClosestPeer(p, bf)
194}
195
196// IsClosestPeer returns true if p is the closest peer for k. Peers with a
197// positive test in the Bloom filter are not considered.
198func (rt *RoutingTable) IsClosestPeer(p, k *PeerAddress, bf *PeerBloomFilter) bool {
199 n := rt.SelectClosestPeer(k, bf)
200 return n.Equals(p)
201}
202
203// ComputeOutDegree computes the number of neighbors that a message should be forwarded to.
204// The arguments are the desired replication level, the hop count of the message so far,
205// and the base-2 logarithm of the current network size estimate (L2NSE) as provided by the
206// underlay. The result is the non-negative number of next hops to select.
207func (rt *RoutingTable) ComputeOutDegree(repl, hop int) int {
208 hf := float64(hop)
209 if hf > 4*rt.l2nse {
210 return 0
211 }
212 if hf > 2*rt.l2nse {
213 return 1
214 }
215 if repl == 0 {
216 repl = 1
217 } else if repl > 16 {
218 repl = 16
219 }
220 rm1 := float64(repl - 1)
221 return 1 + int(rm1/(rt.l2nse+rm1*hf))
222}
223
224//======================================================================
225// Routing table buckets
226//======================================================================
227
228// PeerEntry in a k-Bucket: use routing specific attributes
229// for book-keeping
230type PeerEntry struct {
231 addr *PeerAddress // peer address
232 connected bool // is peer connected?
233}
234
235// Bucket holds peer entries with approx. same distance from node
236type Bucket struct {
237 list []*PeerEntry
238 rwlock sync.RWMutex
239}
240
241// NewBucket creates a new entry list of given size
242func NewBucket(n int) *Bucket {
243 return &Bucket{
244 list: make([]*PeerEntry, 0, n),
245 }
246}
247
248// Add peer address to the bucket if there is free space.
249// Returns true if entry is added, false otherwise.
250func (b *Bucket) Add(p *PeerAddress, connected bool) bool {
251 // only one writer and no readers
252 b.rwlock.Lock()
253 defer b.rwlock.Unlock()
254
255 // check for free space in bucket
256 if len(b.list) < numK {
257 // append entry at the end
258 pe := &PeerEntry{
259 addr: p,
260 connected: connected,
261 }
262 b.list = append(b.list, pe)
263 return true
264 }
265 return false
266}
267
268// Remove peer address from the bucket.
269// Returns true if entry is removed (found), false otherwise.
270func (b *Bucket) Remove(p *PeerAddress) bool {
271 // only one writer and no readers
272 b.rwlock.Lock()
273 defer b.rwlock.Unlock()
274
275 for i, pe := range b.list {
276 if pe.addr.Equals(p) {
277 // found entry: remove it
278 b.list = append(b.list[:i], b.list[i+1:]...)
279 return true
280 }
281 }
282 return false
283}
284
285// SelectClosestPeer returns the entry with minimal distance to the given
286// peer address; entries included in the bloom flter are ignored.
287func (b *Bucket) SelectClosestPeer(p *PeerAddress, bf *PeerBloomFilter) (n *PeerAddress, dist *math.Int) {
288 // no writer allowed
289 b.rwlock.RLock()
290 defer b.rwlock.RUnlock()
291
292 for _, pe := range b.list {
293 // skip addresses in bloomfilter
294 if bf.Contains(pe.addr) {
295 continue
296 }
297 // check for shorter distance
298 if d, _ := p.Distance(pe.addr); n == nil || d.Cmp(dist) < 0 {
299 // remember best match
300 dist = d
301 n = pe.addr
302 }
303 }
304 return
305}