Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "routing_table.h"
19 :
20 : #include <dhtnet/multiplexed_socket.h>
21 : #include <opendht/infohash.h>
22 :
23 : #include <math.h>
24 : #include <stdio.h>
25 : #include <iostream>
26 : #include <iterator>
27 : #include <stdlib.h>
28 : #include <time.h>
29 :
30 : constexpr const std::chrono::minutes FIND_PERIOD {10};
31 : using namespace std::placeholders;
32 :
33 : namespace jami {
34 :
35 : using namespace dht;
36 :
37 916 : Bucket::Bucket(const NodeId& id)
38 916 : : lowerLimit_(id)
39 916 : {}
40 :
41 : bool
42 1553 : Bucket::addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
43 : {
44 1553 : return addNode(NodeInfo(socket));
45 : }
46 :
47 : bool
48 1818 : Bucket::addNode(NodeInfo&& info)
49 : {
50 1818 : auto nodeId = info.socket->deviceId();
51 1818 : if (nodes.try_emplace(nodeId, std::move(info)).second) {
52 1817 : connecting_nodes.erase(nodeId);
53 1817 : known_nodes.erase(nodeId);
54 1816 : mobile_nodes.erase(nodeId);
55 1817 : return true;
56 : }
57 1 : return false;
58 : }
59 :
60 : bool
61 1555 : Bucket::removeNode(const NodeId& nodeId)
62 : {
63 1555 : auto node = nodes.find(nodeId);
64 1554 : auto isMobile = node->second.isMobile_;
65 1555 : if (node == nodes.end())
66 3 : return false;
67 1553 : nodes.erase(nodeId);
68 1550 : if (isMobile) {
69 101 : addMobileNode(nodeId);
70 : } else {
71 1449 : addKnownNode(nodeId);
72 : }
73 :
74 1551 : return true;
75 : }
76 :
77 : std::set<NodeId>
78 15113 : Bucket::getNodeIds() const
79 : {
80 15113 : std::set<NodeId> nodesId;
81 61433 : for (auto const& key : nodes)
82 46330 : nodesId.insert(key.first);
83 15098 : return nodesId;
84 0 : }
85 :
86 : bool
87 10575 : Bucket::hasNode(const NodeId& nodeId) const
88 : {
89 10575 : return nodes.find(nodeId) != nodes.end();
90 : }
91 :
92 : bool
93 6570 : Bucket::addKnownNode(const NodeId& nodeId)
94 : {
95 6570 : if (!hasNode(nodeId)) {
96 5136 : if (known_nodes.emplace(nodeId).second) {
97 4791 : return true;
98 : }
99 : }
100 1782 : return false;
101 : }
102 :
103 : NodeId
104 149 : Bucket::getKnownNode(unsigned index) const
105 : {
106 149 : if (index > known_nodes.size()) {
107 1 : throw std::out_of_range("End of table for get known Node Id " + std::to_string(index));
108 : }
109 148 : auto it = known_nodes.begin();
110 148 : std::advance(it, index);
111 :
112 148 : return *it;
113 : }
114 :
115 : bool
116 117 : Bucket::addMobileNode(const NodeId& nodeId)
117 : {
118 117 : if (!hasNode(nodeId)) {
119 115 : if (mobile_nodes.emplace(nodeId).second) {
120 113 : known_nodes.erase(nodeId);
121 114 : return true;
122 : }
123 : }
124 3 : return false;
125 : }
126 :
127 : bool
128 1007 : Bucket::addConnectingNode(const NodeId& nodeId)
129 : {
130 1007 : if (!hasNode(nodeId)) {
131 1007 : if (connecting_nodes.emplace(nodeId).second) {
132 952 : known_nodes.erase(nodeId);
133 953 : mobile_nodes.erase(nodeId);
134 953 : return true;
135 : }
136 : }
137 56 : return false;
138 : }
139 :
140 : std::set<NodeId>
141 1629 : Bucket::getKnownNodesRandom(unsigned numberNodes, std::mt19937_64& rd) const
142 : {
143 1629 : std::set<NodeId> nodesToReturn;
144 :
145 1629 : if (getKnownNodesSize() <= numberNodes)
146 1513 : return getKnownNodes();
147 :
148 116 : std::uniform_int_distribution<unsigned> distrib(0, getKnownNodesSize() - 1);
149 :
150 264 : while (nodesToReturn.size() < numberNodes) {
151 148 : nodesToReturn.emplace(getKnownNode(distrib(rd)));
152 : }
153 :
154 116 : return nodesToReturn;
155 1631 : }
156 :
157 : asio::steady_timer&
158 0 : Bucket::getNodeTimer(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
159 : {
160 0 : auto node = nodes.find(socket->deviceId());
161 0 : if (node == nodes.end()) {
162 0 : throw std::range_error("Unable to find timer " + socket->deviceId().toString());
163 : }
164 0 : return node->second.refresh_timer;
165 : }
166 :
167 : bool
168 15 : Bucket::shutdownNode(const NodeId& nodeId)
169 : {
170 15 : auto node = nodes.find(nodeId);
171 :
172 15 : if (node != nodes.end()) {
173 13 : auto socket = node->second.socket;
174 13 : auto node = socket->deviceId();
175 13 : socket->shutdown();
176 13 : removeNode(node);
177 13 : return true;
178 13 : }
179 2 : return false;
180 : }
181 :
182 : void
183 939 : Bucket::shutdownAllNodes()
184 : {
185 1921 : while (not nodes.empty()) {
186 982 : auto it = nodes.begin();
187 982 : auto socket = it->second.socket;
188 982 : auto nodeId = socket->deviceId();
189 982 : socket->shutdown();
190 982 : removeNode(nodeId);
191 982 : }
192 939 : }
193 :
194 : void
195 0 : Bucket::printBucket(unsigned number) const
196 : {
197 0 : JAMI_ERROR("BUCKET Number: {:d}", number);
198 :
199 0 : unsigned nodeNum = 1;
200 0 : for (auto it = nodes.begin(); it != nodes.end(); ++it) {
201 0 : JAMI_DEBUG("Node {:s} Id: {:s} isMobile: {:s}", std::to_string(nodeNum), it->first.toString(), std::to_string(it->second.isMobile_));
202 0 : nodeNum++;
203 : }
204 0 : JAMI_ERROR("Mobile Nodes");
205 0 : nodeNum = 0;
206 0 : for (auto it = mobile_nodes.begin(); it != mobile_nodes.end(); ++it) {
207 0 : JAMI_DEBUG("Node {:s} Id: {:s}", std::to_string(nodeNum), (*it).toString());
208 0 : nodeNum++;
209 : }
210 :
211 0 : JAMI_ERROR("Known Nodes");
212 0 : nodeNum = 0;
213 0 : for (auto it = known_nodes.begin(); it != known_nodes.end(); ++it) {
214 0 : JAMI_DEBUG("Node {:s} Id: {:s}", std::to_string(nodeNum), (*it).toString());
215 0 : nodeNum++;
216 : }
217 0 : JAMI_ERROR("Connecting_nodes");
218 0 : nodeNum = 0;
219 0 : for (auto it = connecting_nodes.begin(); it != connecting_nodes.end(); ++it) {
220 0 : JAMI_DEBUG("Node {:s} Id: {:s}", std::to_string(nodeNum), (*it).toString());
221 0 : nodeNum++;
222 : }
223 0 : };
224 :
225 : void
226 200 : Bucket::changeMobility(const NodeId& nodeId, bool isMobile)
227 : {
228 200 : auto itn = nodes.find(nodeId);
229 199 : if (itn != nodes.end()) {
230 199 : itn->second.isMobile_ = isMobile;
231 : }
232 199 : }
233 :
234 : // For tests
235 :
236 : std::set<std::shared_ptr<dhtnet::ChannelSocketInterface>>
237 1 : Bucket::getNodeSockets() const
238 : {
239 1 : std::set<std::shared_ptr<dhtnet::ChannelSocketInterface>> sockets;
240 3 : for (auto const& info : nodes)
241 2 : sockets.insert(info.second.socket);
242 1 : return sockets;
243 0 : }
244 :
245 : // ####################################################################################################
246 :
247 587 : RoutingTable::RoutingTable()
248 : {
249 587 : buckets.emplace_back(NodeId::zero());
250 587 : }
251 :
252 : bool
253 10 : RoutingTable::addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
254 : {
255 10 : auto bucket = findBucket(socket->deviceId());
256 20 : return addNode(socket, bucket);
257 : }
258 :
259 : bool
260 2193 : RoutingTable::addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& channel,
261 : std::list<Bucket>::iterator& bucket)
262 : {
263 2193 : NodeId nodeId = channel->deviceId();
264 :
265 2193 : if (bucket->hasNode(nodeId) || id_ == nodeId) {
266 672 : return false;
267 : }
268 :
269 1848 : while (bucket->isFull()) {
270 470 : if (contains(bucket, id_)) {
271 327 : split(bucket);
272 327 : bucket = findBucket(nodeId);
273 :
274 : } else {
275 143 : return bucket->addNode(std::move(channel));
276 : }
277 : }
278 1377 : return bucket->addNode(std::move(channel));
279 : }
280 :
281 : bool
282 556 : RoutingTable::removeNode(const NodeId& nodeId)
283 : {
284 556 : return findBucket(nodeId)->removeNode(nodeId);
285 : }
286 :
287 : bool
288 691 : RoutingTable::hasNode(const NodeId& nodeId)
289 : {
290 691 : return findBucket(nodeId)->hasNode(nodeId);
291 : }
292 :
293 : bool
294 5990 : RoutingTable::addKnownNode(const NodeId& nodeId)
295 : {
296 5990 : if (id_ == nodeId)
297 1227 : return false;
298 :
299 4765 : auto bucket = findBucket(nodeId);
300 4768 : if (bucket == buckets.end())
301 0 : return false;
302 :
303 4768 : return bucket->addKnownNode(nodeId);
304 : }
305 :
306 : bool
307 17 : RoutingTable::addMobileNode(const NodeId& nodeId)
308 : {
309 17 : if (id_ == nodeId)
310 1 : return false;
311 :
312 16 : auto bucket = findBucket(nodeId);
313 :
314 16 : if (bucket == buckets.end())
315 0 : return 0;
316 :
317 16 : bucket->addMobileNode(nodeId);
318 16 : return 1;
319 : }
320 :
321 : void
322 2 : RoutingTable::removeMobileNode(const NodeId& nodeId)
323 : {
324 2 : return findBucket(nodeId)->removeMobileNode(nodeId);
325 : }
326 :
327 : bool
328 7 : RoutingTable::hasMobileNode(const NodeId& nodeId)
329 : {
330 7 : return findBucket(nodeId)->hasMobileNode(nodeId);
331 : };
332 :
333 : bool
334 890 : RoutingTable::addConnectingNode(const NodeId& nodeId)
335 : {
336 890 : if (id_ == nodeId)
337 1 : return false;
338 :
339 887 : auto bucket = findBucket(nodeId);
340 :
341 890 : if (bucket == buckets.end())
342 0 : return 0;
343 :
344 887 : bucket->addConnectingNode(nodeId);
345 889 : return 1;
346 : }
347 :
348 : void
349 0 : RoutingTable::removeConnectingNode(const NodeId& nodeId)
350 : {
351 0 : findBucket(nodeId)->removeConnectingNode(nodeId);
352 0 : }
353 :
354 : std::list<Bucket>::iterator
355 16503 : RoutingTable::findBucket(const NodeId& nodeId)
356 : {
357 16503 : if (buckets.empty())
358 0 : throw std::runtime_error("No bucket");
359 :
360 16502 : auto b = buckets.begin();
361 :
362 : while (true) {
363 29084 : auto next = std::next(b);
364 29045 : if (next == buckets.end())
365 16513 : return b;
366 18838 : if (std::memcmp(nodeId.data(), next->getLowerLimit().data(), nodeId.size()) < 0)
367 6290 : return b;
368 12585 : b = next;
369 12585 : }
370 : }
371 :
372 : std::vector<NodeId>
373 1418 : RoutingTable::closestNodes(const NodeId& nodeId, unsigned count)
374 : {
375 1418 : std::vector<NodeId> closestNodes;
376 1420 : auto bucket = findBucket(nodeId);
377 3901 : auto sortedBucketInsert = [&](const std::list<Bucket>::iterator& b) {
378 3901 : auto nodes = b->getNodeIds();
379 14973 : for (auto n : nodes) {
380 11077 : if (n != nodeId) {
381 9676 : auto here = std::find_if(closestNodes.begin(),
382 : closestNodes.end(),
383 53372 : [&nodeId, &n](NodeId& NodeId) {
384 53372 : return nodeId.xorCmp(n, NodeId) < 0;
385 : });
386 :
387 9675 : closestNodes.insert(here, n);
388 : }
389 : }
390 3877 : };
391 :
392 1422 : auto itn = bucket;
393 1422 : auto itp = (bucket == buckets.begin()) ? buckets.end() : std::prev(bucket);
394 4540 : while (itn != buckets.end() || itp != buckets.end()) {
395 3115 : if (itn != buckets.end()) {
396 2549 : sortedBucketInsert(itn);
397 2550 : itn = std::next(itn);
398 : }
399 3117 : if (itp != buckets.end()) {
400 1357 : sortedBucketInsert(itp);
401 1359 : itp = (itp == buckets.begin()) ? buckets.end() : std::prev(itp);
402 : }
403 : }
404 :
405 1428 : if (closestNodes.size() > count) {
406 865 : closestNodes.resize(count);
407 : }
408 :
409 2856 : return closestNodes;
410 0 : }
411 :
412 : void
413 0 : RoutingTable::printRoutingTable() const
414 : {
415 0 : int counter = 1;
416 0 : JAMI_DEBUG("SWARM: {:s} ", id_.toString());
417 0 : for (auto it = buckets.begin(); it != buckets.end(); ++it) {
418 0 : it->printBucket(counter);
419 0 : counter++;
420 : }
421 0 : JAMI_DEBUG("_____________________________________________________________________________");
422 0 : }
423 :
424 : void
425 2 : RoutingTable::shutdownNode(const NodeId& nodeId)
426 : {
427 2 : findBucket(nodeId)->shutdownNode(nodeId);
428 2 : }
429 :
430 : std::vector<NodeId>
431 4612 : RoutingTable::getNodes() const
432 : {
433 4612 : std::lock_guard lock(mutex_);
434 4612 : std::vector<NodeId> ret;
435 15795 : for (const auto& b : buckets) {
436 11183 : const auto& nodes = b.getNodeIds();
437 11184 : ret.insert(ret.end(), nodes.begin(), nodes.end());
438 11183 : }
439 9222 : return ret;
440 4611 : }
441 :
442 : std::vector<NodeId>
443 1 : RoutingTable::getKnownNodes() const
444 : {
445 1 : std::vector<NodeId> ret;
446 2 : for (const auto& b : buckets) {
447 1 : const auto& nodes = b.getKnownNodes();
448 1 : ret.insert(ret.end(), nodes.begin(), nodes.end());
449 : }
450 1 : return ret;
451 0 : }
452 :
453 : std::vector<NodeId>
454 1967 : RoutingTable::getMobileNodes() const
455 : {
456 1967 : std::vector<NodeId> ret;
457 7080 : for (const auto& b : buckets) {
458 5113 : const auto& nodes = b.getMobileNodes();
459 5113 : ret.insert(ret.end(), nodes.begin(), nodes.end());
460 : }
461 1967 : return ret;
462 0 : }
463 :
464 : std::vector<NodeId>
465 1 : RoutingTable::getConnectingNodes() const
466 : {
467 1 : std::vector<NodeId> ret;
468 2 : for (const auto& b : buckets) {
469 1 : const auto& nodes = b.getConnectingNodes();
470 1 : ret.insert(ret.end(), nodes.begin(), nodes.end());
471 : }
472 1 : return ret;
473 0 : }
474 :
475 : std::vector<NodeId>
476 0 : RoutingTable::getBucketMobileNodes() const
477 : {
478 0 : std::vector<NodeId> ret;
479 0 : auto bucket = findBucket(id_);
480 0 : const auto& nodes = bucket->getMobileNodes();
481 0 : ret.insert(ret.end(), nodes.begin(), nodes.end());
482 :
483 0 : return ret;
484 0 : }
485 :
486 : bool
487 4891 : RoutingTable::contains(const std::list<Bucket>::iterator& bucket, const NodeId& nodeId) const
488 : {
489 4891 : return NodeId::cmp(bucket->getLowerLimit(), nodeId) <= 0
490 7940 : && (std::next(bucket) == buckets.end()
491 7936 : || NodeId::cmp(nodeId, std::next(bucket)->getLowerLimit()) < 0);
492 : }
493 :
494 : std::vector<NodeId>
495 17 : RoutingTable::getAllNodes() const
496 : {
497 17 : std::vector<NodeId> ret;
498 34 : for (const auto& b : buckets) {
499 17 : const auto& nodes = b.getNodeIds();
500 17 : const auto& knownNodes = b.getKnownNodes();
501 17 : const auto& mobileNodes = b.getMobileNodes();
502 17 : const auto& connectingNodes = b.getConnectingNodes();
503 17 : ret.reserve(nodes.size() + knownNodes.size() + mobileNodes.size() + connectingNodes.size());
504 17 : ret.insert(ret.end(), nodes.begin(), nodes.end());
505 17 : ret.insert(ret.end(), knownNodes.begin(), knownNodes.end());
506 17 : ret.insert(ret.end(), mobileNodes.begin(), mobileNodes.end());
507 17 : ret.insert(ret.end(), connectingNodes.begin(), connectingNodes.end());
508 17 : }
509 17 : return ret;
510 0 : }
511 :
512 : void
513 12 : RoutingTable::deleteNode(const NodeId& nodeId)
514 : {
515 12 : auto bucket = findBucket(nodeId);
516 12 : bucket->shutdownNode(nodeId);
517 12 : bucket->removeConnectingNode(nodeId);
518 12 : bucket->removeKnownNode(nodeId);
519 12 : bucket->removeMobileNode(nodeId);
520 12 : }
521 :
522 : NodeId
523 327 : RoutingTable::middle(std::list<Bucket>::iterator& it) const
524 : {
525 327 : unsigned bit = depth(it);
526 327 : if (bit >= 8 * HASH_LEN)
527 0 : throw std::out_of_range("End of table");
528 :
529 327 : NodeId id = it->getLowerLimit();
530 327 : id.setBit(bit, true);
531 327 : return id;
532 : }
533 :
534 : unsigned
535 327 : RoutingTable::depth(std::list<Bucket>::iterator& bucket) const
536 : {
537 327 : int bit1 = bucket->getLowerLimit().lowbit();
538 327 : int bit2 = std::next(bucket) != buckets.end() ? std::next(bucket)->getLowerLimit().lowbit()
539 327 : : -1;
540 327 : return std::max(bit1, bit2) + 1;
541 : }
542 :
543 : bool
544 327 : RoutingTable::split(std::list<Bucket>::iterator& bucket)
545 : {
546 327 : NodeId id = middle(bucket);
547 327 : auto newBucketIt = buckets.emplace(std::next(bucket), id);
548 : // Re-assign nodes
549 327 : auto& nodeSwap = bucket->getNodes();
550 :
551 981 : for (auto it = nodeSwap.begin(); it != nodeSwap.end();) {
552 654 : auto& node = *it;
553 :
554 654 : auto nodeId = it->first;
555 :
556 654 : if (!contains(bucket, nodeId)) {
557 264 : newBucketIt->addNode(std::move(node.second));
558 264 : it = nodeSwap.erase(it);
559 : } else {
560 390 : ++it;
561 : }
562 : }
563 :
564 327 : auto connectingSwap = bucket->getConnectingNodes();
565 583 : for (auto it = connectingSwap.begin(); it != connectingSwap.end();) {
566 256 : auto nodeId = *it;
567 :
568 256 : if (!contains(bucket, nodeId)) {
569 117 : newBucketIt->addConnectingNode(nodeId);
570 117 : it = connectingSwap.erase(it);
571 117 : bucket->removeConnectingNode(nodeId);
572 : } else {
573 139 : ++it;
574 : }
575 : }
576 :
577 327 : auto knownSwap = bucket->getKnownNodes();
578 838 : for (auto it = knownSwap.begin(); it != knownSwap.end();) {
579 511 : auto nodeId = *it;
580 :
581 511 : if (!contains(bucket, nodeId)) {
582 270 : newBucketIt->addKnownNode(nodeId);
583 270 : it = knownSwap.erase(it);
584 270 : bucket->removeKnownNode(nodeId);
585 : } else {
586 241 : ++it;
587 : }
588 : }
589 :
590 327 : auto mobileSwap = bucket->getMobileNodes();
591 327 : for (auto it = mobileSwap.begin(); it != mobileSwap.end();) {
592 0 : auto nodeId = *it;
593 :
594 0 : if (!contains(bucket, nodeId)) {
595 0 : newBucketIt->addMobileNode(nodeId);
596 0 : it = mobileSwap.erase(it);
597 0 : bucket->removeMobileNode(nodeId);
598 : } else {
599 0 : ++it;
600 : }
601 : }
602 :
603 327 : return true;
604 327 : }
605 :
606 : } // namespace jami
|