Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 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 : #pragma once
18 :
19 : #include "manager.h"
20 :
21 : #include <dhtnet/multiplexed_socket.h>
22 :
23 : #include <opendht/infohash.h>
24 :
25 : #include <asio.hpp>
26 : #include <asio/detail/deadline_timer_service.hpp>
27 :
28 : #include <utility>
29 : #include <vector>
30 : #include <memory>
31 : #include <list>
32 : #include <set>
33 :
34 : using NodeId = dht::PkId;
35 :
36 : namespace jami {
37 :
38 : static constexpr const std::chrono::minutes FIND_PERIOD {10};
39 :
40 : struct NodeInfo
41 : {
42 : bool isMobile_ {false};
43 : std::shared_ptr<dhtnet::ChannelSocketInterface> socket {};
44 : asio::steady_timer refresh_timer {*Manager::instance().ioContext(), FIND_PERIOD};
45 : NodeInfo() = delete;
46 1351 : NodeInfo(NodeInfo&&) noexcept = default;
47 1065 : NodeInfo(std::shared_ptr<dhtnet::ChannelSocketInterface> socket_)
48 1065 : : socket(std::move(socket_))
49 1065 : {}
50 1 : NodeInfo(bool mobile, std::shared_ptr<dhtnet::ChannelSocketInterface> socket_)
51 1 : : isMobile_(mobile)
52 1 : , socket(std::move(socket_))
53 1 : {}
54 : };
55 :
56 : class Bucket
57 : {
58 : public:
59 : static constexpr int BUCKET_MAX_SIZE = 2;
60 :
61 : Bucket() = delete;
62 : Bucket(const Bucket&) = delete;
63 : Bucket(const NodeId&);
64 :
65 : /**
66 : * Add Node socket to bucket
67 : * @param socket
68 : * @return true if node was added, false if not
69 : */
70 : bool addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket);
71 :
72 : /**
73 : * Add NodeInfo to bucket
74 : * @param nodeInfo
75 : * @return true if node was added, false if not
76 : */
77 : bool addNode(NodeInfo&& info);
78 :
79 : /**
80 : * Remove NodeId socket from bucket and insert it in known_nodes or
81 : * mobile_nodes depending on its type
82 : * @param nodeId
83 : * @return true if node was removed, false if not
84 : */
85 : bool removeNode(const NodeId& nodeId);
86 :
87 : /**
88 : * Get connected nodes from bucket
89 : * @return map of NodeId and NodeInfo
90 : */
91 4268 : const std::map<NodeId, NodeInfo>& getNodes() const { return nodes; }
92 319 : std::map<NodeId, NodeInfo>& getNodes() { return nodes; }
93 :
94 : /**
95 : * Get NodeIds from bucket
96 : * @return set of NodeIds
97 : */
98 : std::set<NodeId> getNodeIds() const;
99 :
100 : /**
101 : * Test if socket exists in nodes
102 : * @param nodeId
103 : * @return true if node exists, false if not
104 : */
105 : bool hasNode(const NodeId& nodeId) const;
106 :
107 : /**
108 : * Add NodeId to known_nodes if it doesn't exist in nodes
109 : * @param nodeId
110 : * @return true if known node was added, false if not
111 : */
112 : bool addKnownNode(const NodeId& nodeId);
113 :
114 : /**
115 : * Remove NodeId from known_nodes
116 : * @param nodeId
117 : */
118 97 : void removeKnownNode(const NodeId& nodeId) { known_nodes.erase(nodeId); }
119 :
120 : /**
121 : * Get NodeIds from known_nodes
122 : * @return set of known NodeIds
123 : */
124 1550 : const std::set<NodeId>& getKnownNodes() const { return known_nodes; }
125 :
126 : /**
127 : * Returns NodeId from known_nodes at index
128 : * @param index
129 : * @return NodeId
130 : */
131 : NodeId getKnownNode(unsigned index) const;
132 :
133 : /**
134 : * Test if NodeId exist in known_nodes
135 : * @param nodeId
136 : * @return true if known node exists, false if not
137 : */
138 11 : bool hasKnownNode(const NodeId& nodeId) const { return known_nodes.find(nodeId) != known_nodes.end(); }
139 :
140 : /**
141 : * Add NodeId to mobile_nodes if it doesn't exist in nodes
142 : * @param nodeId
143 : * @return true if mobile node was added, false if not
144 : */
145 : bool addMobileNode(const NodeId& nodeId);
146 :
147 : /**
148 : * Remove NodeId from mobile_nodes
149 : * @param nodeId
150 : */
151 4 : void removeMobileNode(const NodeId& nodeId) { mobile_nodes.erase(nodeId); }
152 :
153 : /**
154 : * Test if NodeId exist in mobile_nodes
155 : * @param nodeId
156 : * @return true if mobile node exists, false if not
157 : */
158 13 : bool hasMobileNode(const NodeId& nodeId) { return mobile_nodes.find(nodeId) != mobile_nodes.end(); }
159 :
160 : /**
161 : * Get NodeIds from mobile_nodes
162 : * @return set of mobile NodeIds
163 : */
164 5548 : const std::set<NodeId>& getMobileNodes() const { return mobile_nodes; }
165 :
166 : /**
167 : * Add NodeId to connecting_nodes if it doesn't exist in nodes
168 : * @param nodeId
169 : * @param nodeInfo
170 : * @return true if connecting node was added, false if not
171 : */
172 : bool addConnectingNode(const NodeId& nodeId);
173 :
174 : /**
175 : * Remove NodeId from connecting_nodes
176 : * @param nodeId
177 : */
178 203 : void removeConnectingNode(const NodeId& nodeId) { connecting_nodes.erase(nodeId); }
179 :
180 : /** Get NodeIds of connecting_nodes
181 : * @return set of connecting NodeIds
182 : */
183 338 : const std::set<NodeId>& getConnectingNodes() const { return connecting_nodes; };
184 :
185 : /**
186 : * Test if NodeId exist in connecting_nodes
187 : * @param nodeId
188 : * @return true if connecting node exists, false if not
189 : */
190 18 : bool hasConnectingNode(const NodeId& nodeId) const
191 : {
192 18 : return connecting_nodes.find(nodeId) != connecting_nodes.end();
193 : }
194 :
195 3868 : bool isEmpty() const { return nodes.empty(); }
196 :
197 : /**
198 : * Indicate if bucket is full
199 : * @return true if bucket is full, false if not
200 : */
201 1353 : bool isFull() const { return nodes.size() == BUCKET_MAX_SIZE; };
202 :
203 : /**
204 : * Returns random numberNodes NodeId from known_nodes
205 : * @param numberNodes
206 : * @param rd
207 : * @return set of numberNodes random known NodeIds
208 : */
209 : std::set<NodeId> getKnownNodesRandom(unsigned numberNodes, std::mt19937_64& rd) const;
210 :
211 : /**
212 : * Returns random NodeId from known_nodes
213 : * @param rd
214 : * @return random known NodeId
215 : */
216 1 : NodeId randomId(std::mt19937_64& rd) const
217 : {
218 1 : auto node = getKnownNodesRandom(1, rd);
219 2 : return *node.begin();
220 1 : }
221 :
222 : /**
223 : * Returns socket's timer
224 : * @param socket
225 : * @return timer
226 : */
227 : asio::steady_timer& getNodeTimer(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket);
228 :
229 : /**
230 : * Shutdowns socket and removes it from nodes.
231 : * The corresponding node is moved to known_nodes or mobile_nodes
232 : * @param socket
233 : * @return true if node was shutdown, false if not found
234 : */
235 : bool shutdownNode(const NodeId& nodeId);
236 :
237 : /**
238 : * Shutdowns all sockets in nodes through shutdownNode
239 : */
240 : void shutdownAllNodes();
241 :
242 : /**
243 : * Prints bucket and bucket's number
244 : */
245 : void printBucket(unsigned number) const;
246 :
247 : /**
248 : * Change mobility of specific node, mobile or not
249 : */
250 : void changeMobility(const NodeId& nodeId, bool isMobile);
251 :
252 : /**
253 : * Returns number of nodes in bucket
254 : * @return size of nodes
255 : */
256 1759 : unsigned getNodesSize() const { return nodes.size(); }
257 :
258 : /**
259 : * Returns number of knwon_nodes in bucket
260 : * @return size of knwon_nodes
261 : */
262 1421 : unsigned getKnownNodesSize() const { return known_nodes.size(); }
263 :
264 : /**
265 : * Returns number of mobile_nodes in bucket
266 : * @return size of mobile_nodes
267 : */
268 2721 : unsigned getConnectingNodesSize() const { return connecting_nodes.size(); }
269 :
270 : /**
271 : * Returns bucket lower limit
272 : * @return NodeId lower limit
273 : */
274 17286 : NodeId getLowerLimit() const { return lowerLimit_; };
275 :
276 : /**
277 : * Set bucket's lower limit
278 : * @param nodeId
279 : */
280 : void setLowerLimit(const NodeId& nodeId) { lowerLimit_ = nodeId; }
281 :
282 : // For tests
283 :
284 : /**
285 : * Get sockets from bucket
286 : * @return set of sockets
287 : */
288 : std::set<std::shared_ptr<dhtnet::ChannelSocketInterface>> getNodeSockets() const;
289 :
290 : private:
291 : NodeId lowerLimit_;
292 : std::map<NodeId, NodeInfo> nodes;
293 : std::set<NodeId> known_nodes;
294 : std::set<NodeId> connecting_nodes;
295 : std::set<NodeId> mobile_nodes;
296 : mutable std::mutex mutex;
297 : };
298 :
299 : // ####################################################################################################
300 :
301 : class RoutingTable
302 : {
303 : public:
304 : RoutingTable();
305 :
306 : bool isEmpty() const;
307 :
308 : /**
309 : * Add socket to bucket
310 : * @param socket
311 : * @return true if socket was added, false if not
312 : */
313 : bool addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket);
314 :
315 : /**
316 : * Add socket to specific bucket
317 : * @param channel
318 : * @param bucket
319 : * @return true if socket was added to bucket, false if not
320 : */
321 : bool addNode(const std::shared_ptr<dhtnet::ChannelSocketInterface>& channel, std::list<Bucket>::iterator& bucket);
322 :
323 : /**
324 : * Removes node from routing table
325 : * Adds it to known_nodes or mobile_nodes depending on mobility
326 : * @param socket
327 : * @return true if node was removed, false if not
328 : */
329 : bool removeNode(const NodeId& nodeId);
330 :
331 : /**
332 : * Check if connected node exsits in routing table
333 : * @param nodeId
334 : * @return true if node exists, false if not
335 : */
336 : bool hasNode(const NodeId& nodeId);
337 :
338 : /**
339 : * Add known node to routing table
340 : * @param nodeId
341 : * @return true if known node was added, false if not
342 : */
343 : bool addKnownNode(const NodeId& nodeId);
344 :
345 : /**
346 : * Checks if known node exists in routing table
347 : * @param nodeId
348 : * @return true if known node exists, false if not
349 : */
350 4 : bool hasKnownNode(const NodeId& nodeId) const
351 : {
352 4 : auto bucket = findBucket(nodeId);
353 8 : return bucket->hasKnownNode(nodeId);
354 : }
355 :
356 : /**
357 : * Add mobile node to routing table
358 : * @param nodeId
359 : * @return true if mobile node was added, false if not
360 : */
361 : bool addMobileNode(const NodeId& nodeId);
362 :
363 : /**
364 : * Remove mobile node to routing table
365 : * @param nodeId
366 : * @return true if mobile node was removed, false if not
367 : */
368 : void removeMobileNode(const NodeId& nodeId);
369 :
370 : /**
371 : * Check if mobile node exists in routing table
372 : * @param nodeId
373 : * @return true if mobile node exists, false if not
374 : */
375 : bool hasMobileNode(const NodeId& nodeId);
376 :
377 : /**
378 : * Add connecting node to routing table
379 : * @param nodeId
380 : * @return true if connecting node was added, false if not
381 : */
382 : bool addConnectingNode(const NodeId& nodeId);
383 :
384 : /**
385 : * Remove connecting connecting node to routing table
386 : * @param nodeId
387 : * @return true if connecting node was removed, false if not
388 : */
389 : void removeConnectingNode(const NodeId& nodeId);
390 :
391 : /**
392 : * Check if Connecting node exists in routing table
393 : * @param nodeId
394 : * @return true if connecting node exists, false if not
395 : */
396 6 : bool hasConnectingNode(const NodeId& nodeId) const
397 : {
398 6 : auto bucket = findBucket(nodeId);
399 12 : return bucket->hasConnectingNode(nodeId);
400 : }
401 :
402 : /**
403 : * Returns bucket iterator containing nodeId
404 : * @param nodeId
405 : * @return bucket iterator
406 : */
407 : std::list<Bucket>::iterator findBucket(const NodeId& nodeId);
408 :
409 : /**
410 : * Returns bucket iterator containing nodeId
411 : * @param nodeId
412 : * @return bucket iterator
413 : */
414 10 : inline const std::list<Bucket>::const_iterator findBucket(const NodeId& nodeId) const
415 : {
416 10 : return std::list<Bucket>::const_iterator(const_cast<RoutingTable*>(this)->findBucket(nodeId));
417 : }
418 :
419 : /**
420 : * Returns the count closest nodes to a specific nodeId
421 : * @param nodeId
422 : * @param count
423 : * @return vector of nodeIds
424 : */
425 : std::vector<NodeId> closestNodes(const NodeId& nodeId, unsigned count);
426 :
427 : /**
428 : * Returns number of buckets in routing table
429 : * @return size of buckets
430 : */
431 : unsigned size() const { return buckets.size(); }
432 :
433 : /**
434 : * Returns number of total nodes in routing table
435 : * @return size of nodes
436 : */
437 10 : unsigned getNodeCount() const
438 : {
439 10 : size_t count = 0;
440 44 : for (const auto& b : buckets)
441 34 : count += b.getNodesSize();
442 10 : return count;
443 : }
444 :
445 472 : unsigned getActiveNodesCount() const
446 : {
447 472 : size_t count = 0;
448 944 : for (const auto& b : buckets)
449 472 : count += b.getNodesSize() + b.getConnectingNodesSize();
450 472 : return count;
451 : }
452 :
453 : /**
454 : * Prints routing table
455 : */
456 : void printRoutingTable() const;
457 :
458 : /**
459 : * Shutdowns a node
460 : * @param nodeId
461 : */
462 : void shutdownNode(const NodeId& nodeId);
463 :
464 : /**
465 : * Shutdowns all nodes in routing table and add them to known_nodes or mobile_nodes
466 : */
467 623 : void shutdownAllNodes()
468 : {
469 1565 : for (auto& bucket : buckets)
470 942 : bucket.shutdownAllNodes();
471 623 : }
472 :
473 : /**
474 : * Sets id for routing table
475 : * @param node
476 : */
477 595 : void setId(const NodeId& node) { id_ = node; }
478 :
479 : /**
480 : * Returns id for routing table
481 : * @return Nodeid
482 : */
483 : NodeId getId() const { return id_; }
484 :
485 : /**
486 : * Returns buckets in routing table
487 : * @return list buckets
488 : */
489 952 : std::list<Bucket>& getBuckets() { return buckets; }
490 :
491 : /**
492 : * Returns all routing table's connected nodes
493 : * @return vector of nodeIds
494 : */
495 : std::vector<NodeId> getNodes() const;
496 :
497 : /**
498 : * Returns all routing table's known nodes
499 : *@return vector of nodeIds
500 : */
501 : std::vector<NodeId> getKnownNodes() const;
502 :
503 : /**
504 : * Returns all routing table's mobile nodes
505 : * @return vector of nodeIds
506 : */
507 : std::vector<NodeId> getMobileNodes() const;
508 :
509 : /**
510 : * Returns all routing table's connecting nodes
511 : * @return vector of nodeIds
512 : */
513 : std::vector<NodeId> getConnectingNodes() const;
514 :
515 : /**
516 : * Returns mobile nodes corresponding to the swarm's id
517 : * @return vector of nodeIds
518 : */
519 : std::vector<NodeId> getBucketMobileNodes() const;
520 :
521 : std::vector<NodeId> getConnectedNodes() const;
522 :
523 : /**
524 : * Test if connected nodeId is in specific bucket
525 : * @param it
526 : * @param nodeId
527 : * @return true if nodeId is in bucket, false if not
528 : */
529 : bool contains(const std::list<Bucket>::iterator& it, const NodeId& nodeId) const;
530 :
531 : /**
532 : * Return every node from each bucket
533 : */
534 : std::vector<NodeId> getAllNodes() const;
535 :
536 : /**
537 : * Delete node from every table in bucket
538 : */
539 : void deleteNode(const NodeId& nodeId);
540 :
541 : struct NodeStats
542 : {
543 : std::string id;
544 : std::string status;
545 : std::string remoteAddress;
546 : std::chrono::system_clock::time_point connectionTime;
547 : bool isMobile;
548 : };
549 : std::vector<NodeStats> getRoutingTableStats() const;
550 :
551 : private:
552 : RoutingTable(const RoutingTable&) = delete;
553 : RoutingTable& operator=(const RoutingTable&) = delete;
554 :
555 : /**
556 : * Returns middle of routing table
557 : * @param it
558 : * @return NodeId
559 : */
560 : NodeId middle(std::list<Bucket>::iterator& it) const;
561 :
562 : /**
563 : * Returns depth of routing table
564 : * @param bucket
565 : * @return depth
566 : */
567 : unsigned depth(std::list<Bucket>::iterator& bucket) const;
568 :
569 : /**
570 : * Splits bucket
571 : * @param bucket
572 : * @return true if bucket was split, false if not
573 : */
574 : bool split(std::list<Bucket>::iterator& bucket);
575 :
576 : NodeId id_;
577 :
578 : std::list<Bucket> buckets;
579 :
580 : mutable std::mutex mutex_;
581 : };
582 : }; // namespace jami
|