xrpld
Loading...
Searching...
No Matches
peerfinder/detail/Logic.h
1#pragma once
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/basics/random.h>
6#include <xrpl/beast/net/IPAddress.h>
7#include <xrpl/beast/net/IPAddressConversion.h>
8#include <xrpl/beast/net/IPEndpoint.h>
9#include <xrpl/beast/utility/Journal.h>
10#include <xrpl/beast/utility/PropertyStream.h>
11#include <xrpl/beast/utility/WrappedSink.h>
12#include <xrpl/beast/utility/instrumentation.h>
13#include <xrpl/peerfinder/Config.h>
14#include <xrpl/peerfinder/Slot.h>
15#include <xrpl/peerfinder/Types.h>
16#include <xrpl/peerfinder/detail/Bootcache.h>
17#include <xrpl/peerfinder/detail/Counts.h>
18#include <xrpl/peerfinder/detail/Fixed.h>
19#include <xrpl/peerfinder/detail/Handouts.h>
20#include <xrpl/peerfinder/detail/Livecache.h>
21#include <xrpl/peerfinder/detail/SlotImp.h>
22#include <xrpl/peerfinder/detail/Source.h>
23#include <xrpl/peerfinder/detail/Store.h>
24#include <xrpl/protocol/PublicKey.h>
25
26#include <boost/asio/error.hpp>
27
28#include <algorithm>
29#include <cstddef>
30#include <cstdint>
31#include <functional>
32#include <iomanip>
33#include <ios>
34#include <map>
35#include <memory>
36#include <mutex>
37#include <optional>
38#include <set>
39#include <stdexcept>
40#include <string>
41#include <string_view>
42#include <tuple>
43#include <utility>
44#include <vector>
45
46namespace xrpl::peer_finder {
47
53template <class Checker>
54class Logic
55{
56public:
57 // Maps remote endpoints to slots. Since a slot has a
58 // remote endpoint upon construction, this holds all counts_.
59 //
61
66
68
69 // True if we are stopping.
70 bool stopping = false;
71
72 // The source we are currently fetching.
73 // This is used to cancel I/O during program exit.
75
76private:
77 // Configuration settings
79
80 // Slot counts and other aggregate statistics.
82
83 // A list of slots that should always be connected
85
86public:
87 // Live livecache from mtENDPOINTS messages
89
90 // LiveCache of addresses suitable for gaining initial connections
92
93 // Holds all counts
95
96 // The addresses (but not port) we are connected to. This includes
97 // outgoing connection attempts. Note that this set can contain
98 // duplicates (since the port is not set)
100
101 // Set of public keys belonging to active peers
103
104 // A list of dynamic sources to consult as a fallback
106
108
110
111 //--------------------------------------------------------------------------
112public:
125
126 // Load persistent state information from the Store
127 //
128 void
130 {
131 std::scoped_lock const _(lock);
132 bootcache.load();
133 }
134
142 void
144 {
145 std::scoped_lock const _(lock);
146 stopping = true;
147 if (fetchSource != nullptr)
148 fetchSource->cancel();
149 }
150
151 //--------------------------------------------------------------------------
152 //
153 // Manager
154 //
155 //--------------------------------------------------------------------------
156
157 void
158 config(Config const& c)
159 {
160 std::scoped_lock const _(lock);
161 config_ = c;
162 counts_.onConfig(config_);
163 }
164
165 Config
167 {
168 std::scoped_lock const _(lock);
169 return config_;
170 }
171
172 void
177
178 void
180 {
181 std::scoped_lock const _(lock);
182
183 if (addresses.empty())
184 {
185 JLOG(journal.info()) << "Could not resolve fixed slot '" << name << "'";
186 return;
187 }
188
189 for (auto const& remoteAddress : addresses)
190 {
191 if (remoteAddress.port() == 0)
192 {
194 "Port not specified for address:" + remoteAddress.toString());
195 }
196
197 auto result(fixed_.emplace(
199 std::forward_as_tuple(remoteAddress),
201
202 if (result.second)
203 {
204 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic add fixed '" << name
205 << "' at " << remoteAddress;
206 return;
207 }
208 }
209 }
210
211 //--------------------------------------------------------------------------
212
213 // Called when the Checker completes a connectivity test
214 void
216 beast::ip::Endpoint const& remoteAddress,
217 beast::ip::Endpoint const& checkedAddress,
218 boost::system::error_code ec)
219 {
220 if (ec == boost::asio::error::operation_aborted)
221 return;
222
223 std::scoped_lock const _(lock);
224 auto const iter(slots.find(remoteAddress));
225 if (iter == slots.end())
226 {
227 // The slot disconnected before we finished the check
228 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic tested " << checkedAddress
229 << " but the connection was closed";
230 return;
231 }
232
233 SlotImp& slot(*iter->second);
234 slot.checked = true;
235 slot.connectivityCheckInProgress = false;
236
237 beast::WrappedSink sink{journal.sink(), slot.prefix()};
238 beast::Journal const journal{sink};
239
240 if (ec)
241 {
242 // VFALCO TODO Should we retry depending on the error?
243 slot.canAccept = false;
244 JLOG(journal.error()) << "Logic testing " << iter->first << " with error, "
245 << ec.message();
246 bootcache.onFailure(checkedAddress);
247 return;
248 }
249
250 slot.canAccept = true;
251 slot.setListeningPort(checkedAddress.port());
252 JLOG(journal.debug()) << "Logic testing " << checkedAddress << " succeeded";
253 }
254
255 //--------------------------------------------------------------------------
256
259 beast::ip::Endpoint const& localEndpoint,
260 beast::ip::Endpoint const& remoteEndpoint)
261 {
262 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic accept" << remoteEndpoint
263 << " on local " << localEndpoint;
264
265 std::scoped_lock const _(lock);
266
267 // Check for connection limit per address
268 if (isPublic(remoteEndpoint))
269 {
270 auto const count = connectedAddresses.count(remoteEndpoint.address());
271 if (count + 1 > config_.ipLimit)
272 {
273 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic dropping inbound "
274 << remoteEndpoint << " because of ip limits.";
275 return {SlotImp::ptr(), Result::IpLimitExceeded};
276 }
277 }
278
279 // Check for duplicate connection
280 if (slots.contains(remoteEndpoint))
281 {
282 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic dropping "
283 << remoteEndpoint << " as duplicate incoming";
284 return {SlotImp::ptr(), Result::DuplicatePeer};
285 }
286
287 // Create the slot
288 SlotImp::ptr const slot(
290 localEndpoint, remoteEndpoint, fixed(remoteEndpoint.address()), clock));
291 // Add slot to table
292 auto const result(slots.emplace(slot->remoteEndpoint(), slot));
293 // Remote address must not already exist
294 XRPL_ASSERT(
295 result.second,
296 "xrpl::peer_finder::Logic::new_inbound_slot : remote endpoint "
297 "inserted");
298 // Add to the connected address list
299 connectedAddresses.emplace(remoteEndpoint.address());
300
301 // Update counts
302 counts_.add(*slot);
303
304 return {result.first->second, Result::Success};
305 }
306
307 // Can't check for self-connect because we don't know the local endpoint
310 {
311 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic connect " << remoteEndpoint;
312
313 std::scoped_lock const _(lock);
314
315 // Check for duplicate connection
316 if (slots.contains(remoteEndpoint))
317 {
318 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic dropping "
319 << remoteEndpoint << " as duplicate connect";
320 return {SlotImp::ptr(), Result::DuplicatePeer};
321 }
322
323 // Create the slot
324 SlotImp::ptr const slot(
325 std::make_shared<SlotImp>(remoteEndpoint, fixed(remoteEndpoint), clock));
326
327 // Add slot to table
328 auto const result = slots.emplace(slot->remoteEndpoint(), slot);
329 // Remote address must not already exist
330 XRPL_ASSERT(
331 result.second,
332 "xrpl::peer_finder::Logic::new_outbound_slot : remote endpoint "
333 "inserted");
334
335 // Add to the connected address list
336 connectedAddresses.emplace(remoteEndpoint.address());
337
338 // Update counts
339 counts_.add(*slot);
340
341 return {result.first->second, Result::Success};
342 }
343
344 bool
345 onConnected(SlotImp::ptr const& slot, beast::ip::Endpoint const& localEndpoint)
346 {
347 beast::WrappedSink sink{journal.sink(), slot->prefix()};
348 beast::Journal const journal{sink};
349
350 JLOG(journal.trace()) << "Logic connected on local " << localEndpoint;
351
352 std::scoped_lock const _(lock);
353
354 // The object must exist in our table
355 XRPL_ASSERT(
356 slots.contains(slot->remoteEndpoint()),
357 "xrpl::peer_finder::Logic::onConnected : valid slot input");
358 // Assign the local endpoint now that it's known
359 slot->localEndpoint(localEndpoint);
360
361 // Check for self-connect by address
362 {
363 auto const iter(slots.find(localEndpoint));
364 if (iter != slots.end())
365 {
366 XRPL_ASSERT(
367 iter->second->localEndpoint() == slot->remoteEndpoint(),
368 "xrpl::peer_finder::Logic::onConnected : local and remote "
369 "endpoints do match");
370 JLOG(journal.warn()) << "Logic dropping as self connect";
371 return false;
372 }
373 }
374
375 // Update counts
376 counts_.remove(*slot);
377 slot->state(Slot::State::Connected);
378 counts_.add(*slot);
379 return true;
380 }
381
382 Result
383 activate(SlotImp::ptr const& slot, PublicKey const& key, bool reserved)
384 {
385 beast::WrappedSink sink{journal.sink(), slot->prefix()};
386 beast::Journal const journal{sink};
387
388 JLOG(journal.debug()) << "Logic handshake " << slot->remoteEndpoint() << " with "
389 << (reserved ? "reserved " : "") << "key " << key;
390
391 std::scoped_lock const _(lock);
392
393 // The object must exist in our table
394 XRPL_ASSERT(
395 slots.contains(slot->remoteEndpoint()),
396 "xrpl::peer_finder::Logic::activate : valid slot input");
397 // Must be accepted or connected
398 XRPL_ASSERT(
399 slot->state() == Slot::State::Accept || slot->state() == Slot::State::Connected,
400 "xrpl::peer_finder::Logic::activate : valid slot state");
401
402 // Check for duplicate connection by key
403 if (keys.contains(key))
404 return Result::DuplicatePeer;
405
406 // If the peer belongs to a cluster or is reserved,
407 // update the slot to reflect that.
408 counts_.remove(*slot);
409 slot->reserved(reserved);
410 counts_.add(*slot);
411
412 // See if we have an open space for this slot
413 if (!counts_.canActivate(*slot))
414 {
415 if (!slot->inbound())
416 bootcache.onSuccess(slot->remoteEndpoint());
417 if (slot->inbound() && counts_.inMax() == 0)
418 return Result::InboundDisabled;
419 return Result::Full;
420 }
421
422 // Set the key right before adding to the map, otherwise we might
423 // assert later when erasing the key.
424 slot->publicKey(key);
425 {
426 [[maybe_unused]] bool const inserted = keys.insert(key).second;
427 // Public key must not already exist
428 XRPL_ASSERT(inserted, "xrpl::peer_finder::Logic::activate : public key inserted");
429 }
430
431 // Change state and update counts
432 counts_.remove(*slot);
433 slot->activate(clock.now());
434 counts_.add(*slot);
435
436 if (!slot->inbound())
437 bootcache.onSuccess(slot->remoteEndpoint());
438
439 // Mark fixed slot success
440 if (slot->fixed() && !slot->inbound())
441 {
442 auto iter(fixed_.find(slot->remoteEndpoint()));
443 if (iter == fixed_.end())
444 {
446 "peer_finder::Logic::activate(): remote_endpoint "
447 "missing from fixed_");
448 }
449
450 iter->second.success(clock.now());
451 JLOG(journal.trace()) << "Logic fixed success";
452 }
453
454 return Result::Success;
455 }
456
464 {
465 std::scoped_lock const _(lock);
466 RedirectHandouts h(slot);
467 livecache.hops.shuffle();
468 handout(&h, (&h) + 1, livecache.hops.begin(), livecache.hops.end());
469 return std::move(h.list());
470 }
471
476 // VFALCO TODO This should add the returned addresses to the
477 // squelch list in one go once the list is built,
478 // rather than having each module add to the squelch list.
481 {
483
484 std::scoped_lock const _(lock);
485
486 // Count how many more outbound attempts to make
487 //
488 auto needed(counts_.attemptsNeeded());
489 if (needed == 0)
490 return none;
491
492 ConnectHandouts h(needed, squelches);
493
494 // Make sure we don't connect to already-connected entries.
495 for (auto const& s : slots)
496 {
497 auto const result(squelches.insert(s.second->remoteEndpoint().address()));
498 if (!result.second)
499 squelches.touch(result.first);
500 }
501
502 // 1. Use Fixed if:
503 // Fixed active count is below fixed count AND
504 // ( There are eligible fixed addresses to try OR
505 // Any outbound attempts are in progress)
506 //
507 if (counts_.fixedActive() < fixed_.size())
508 {
509 getFixed(needed, h.list(), squelches);
510
511 if (!h.list().empty())
512 {
513 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic connect "
514 << h.list().size() << " fixed";
515 return h.list();
516 }
517
518 if (counts_.attempts() > 0)
519 {
520 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic waiting on "
521 << counts_.attempts() << " attempts";
522 return none;
523 }
524 }
525
526 // Only proceed if auto connect is enabled and we
527 // have less than the desired number of outbound slots
528 //
529 if (!config_.autoConnect || counts_.outActive() >= counts_.outMax())
530 return none;
531
532 // 2. Use Livecache if:
533 // There are any entries in the cache OR
534 // Any outbound attempts are in progress
535 //
536 {
537 livecache.hops.shuffle();
538 handout(&h, (&h) + 1, livecache.hops.rbegin(), livecache.hops.rend());
539 if (!h.list().empty())
540 {
541 JLOG(journal.debug())
542 << std::left << std::setw(18) << "Logic connect " << h.list().size() << " live "
543 << ((h.list().size() > 1) ? "endpoints" : "endpoint");
544 return h.list();
545 }
546 if (counts_.attempts() > 0)
547 {
548 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic waiting on "
549 << counts_.attempts() << " attempts";
550 return none;
551 }
552 }
553
554 /* 3. Bootcache refill
555 If the Bootcache is empty, try to get addresses from the current
556 set of Sources and add them into the Bootstrap cache.
557
558 Pseudocode:
559 If ( domainNames.count() > 0 AND (
560 unusedBootstrapIPs.count() == 0
561 OR activeNameResolutions.count() > 0) )
562 ForOneOrMore (DomainName that hasn't been resolved recently)
563 Contact DomainName and add entries to the
564 unusedBootstrapIPs return;
565 */
566
567 // 4. Use Bootcache if:
568 // There are any entries we haven't tried lately
569 //
570 for (auto iter(bootcache.begin()); !h.full() && iter != bootcache.end(); ++iter)
571 h.tryInsert(*iter);
572
573 if (!h.list().empty())
574 {
575 JLOG(journal.debug()) << std::left << std::setw(18) << "Logic connect "
576 << h.list().size() << " boot "
577 << ((h.list().size() > 1) ? "addresses" : "address");
578 return h.list();
579 }
580
581 // If we get here we are stuck
582 return none;
583 }
584
587 {
589
590 std::scoped_lock const _(lock);
591
592 clock_type::time_point const now = clock.now();
593 if (whenBroadcast <= now)
594 {
596
597 {
598 // build list of active slots
599 std::vector<SlotImp::ptr> activeSlots;
600 activeSlots.reserve(slots.size());
601 std::ranges::for_each(slots, [&activeSlots](Slots::value_type const& value) {
602 if (value.second->state() == Slot::State::Active)
603 activeSlots.emplace_back(value.second);
604 });
605 std::shuffle(activeSlots.begin(), activeSlots.end(), defaultPrng());
606
607 // build target vector
608 targets.reserve(activeSlots.size());
609 std::ranges::for_each(activeSlots, [&targets](SlotImp::ptr const& slot) {
610 targets.emplace_back(slot);
611 });
612 }
613
614 /* VFALCO NOTE
615 This is a temporary measure. Once we know our own IP
616 address, the correct solution is to put it into the Livecache
617 at hops 0, and go through the regular handout path. This way
618 we avoid handing our address out too frequently, which this code
619 suffers from.
620 */
621 // Add an entry for ourselves if:
622 // 1. We want incoming
623 // 2. We have slots
624 // 3. We haven't failed the firewalled test
625 //
626 if (config_.wantIncoming && counts_.inMax() > 0)
627 {
628 Endpoint ep;
629 ep.hops = 0;
630 // we use the unspecified (0) address here because the value is
631 // irrelevant to recipients. When peers receive an endpoint
632 // with 0 hops, they use the socket remote_addr instead of the
633 // value in the message. Furthermore, since the address value
634 // is ignored, the type/version (ipv4 vs ipv6) doesn't matter
635 // either. ipv6 has a slightly more compact string
636 // representation of 0, so use that for self entries.
637 ep.address =
639 for (auto& t : targets)
640 t.insert(ep);
641 }
642
643 // build sequence of endpoints by hops
644 livecache.hops.shuffle();
645 handout(targets.begin(), targets.end(), livecache.hops.begin(), livecache.hops.end());
646
647 // broadcast
648 for (auto const& t : targets)
649 {
650 SlotImp::ptr const& slot = t.slot();
651 auto const& list = t.list();
652 beast::WrappedSink sink{journal.sink(), slot->prefix()};
653 beast::Journal const journal{sink};
654 JLOG(journal.trace()) << "Logic sending " << list.size()
655 << ((list.size() == 1) ? " endpoint" : " endpoints");
656 result.emplace_back(slot, list);
657 }
658
660 }
661
662 return result;
663 }
664
665 void
667 {
668 std::scoped_lock const _(lock);
669
670 // Expire the Livecache
671 livecache.expire();
672
673 // Expire the recent cache in each slot
674 for (auto const& entry : slots)
675 entry.second->expire();
676
677 // Expire the recent attempts table
679
680 bootcache.periodicActivity();
681 }
682
683 //--------------------------------------------------------------------------
684
685 // Validate and clean up the list that we received from the slot.
686 void
688 {
689 bool neighbor(false);
690 for (auto iter = list.begin(); iter != list.end();)
691 {
692 Endpoint& ep(*iter);
693
694 // Enforce hop limit
695 if (ep.hops > tuning::kMaxHops)
696 {
697 JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
698 << ep.address << " for excess hops " << ep.hops;
699 iter = list.erase(iter);
700 continue;
701 }
702
703 // See if we are directly connected
704 if (ep.hops == 0)
705 {
706 if (!neighbor)
707 {
708 // Fill in our neighbors remote address
709 neighbor = true;
710 ep.address = slot->remoteEndpoint().atPort(ep.address.port());
711 }
712 else
713 {
714 JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
715 << ep.address << " for extra self";
716 iter = list.erase(iter);
717 continue;
718 }
719 }
720
721 // Discard invalid addresses
722 if (!isValidAddress(ep.address))
723 {
724 JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
725 << ep.address << " as invalid";
726 iter = list.erase(iter);
727 continue;
728 }
729
730 // Filter duplicates
731 if (std::any_of(list.begin(), iter, [ep](Endpoints::value_type const& other) {
732 return ep.address == other.address;
733 }))
734 {
735 JLOG(journal.debug()) << std::left << std::setw(18) << "Endpoints drop "
736 << ep.address << " as duplicate";
737 iter = list.erase(iter);
738 continue;
739 }
740
741 // Increment hop count on the incoming message, so
742 // we store it at the hop count we will send it at.
743 //
744 ++ep.hops;
745
746 ++iter;
747 }
748 }
749
750 void
752 {
753 beast::WrappedSink sink{journal.sink(), slot->prefix()};
754 beast::Journal const journal{sink};
755
756 // If we're sent too many endpoints, sample them at random:
758 {
759 std::shuffle(list.begin(), list.end(), defaultPrng());
761 }
762
763 JLOG(journal.trace()) << "Endpoints contained " << list.size()
764 << ((list.size() > 1) ? " entries" : " entry");
765
766 std::scoped_lock const _(lock);
767
768 // The object must exist in our table
769 XRPL_ASSERT(
770 slots.contains(slot->remoteEndpoint()),
771 "xrpl::peer_finder::Logic::onEndpoints : valid slot input");
772
773 // Must be handshaked!
774 XRPL_ASSERT(
775 slot->state() == Slot::State::Active,
776 "xrpl::peer_finder::Logic::onEndpoints : valid slot state");
777
778 clock_type::time_point const now(clock.now());
779
780 // Limit how often we accept new endpoints
781 if (slot->whenAcceptEndpoints > now)
782 return;
783
784 preprocess(slot, list);
785
786 for (auto const& ep : list)
787 {
788 XRPL_ASSERT(ep.hops, "xrpl::peer_finder::Logic::onEndpoints : nonzero hops");
789
790 slot->recent.insert(ep.address, ep.hops);
791
792 // Note hops has been incremented, so 1
793 // means a directly connected neighbor.
794 //
795 if (ep.hops == 1)
796 {
797 if (slot->connectivityCheckInProgress)
798 {
799 JLOG(journal.debug())
800 << "Logic testing " << ep.address << " already in progress";
801 continue;
802 }
803
804 if (!slot->checked)
805 {
806 // Mark that a check for this slot is now in progress.
807 slot->connectivityCheckInProgress = true;
808
809 // Test the slot's listening port before
810 // adding it to the livecache for the first time.
811 //
812 checker.asyncConnect(
813 ep.address,
814 [this, remoteAddress = slot->remoteEndpoint(), checkedAddress = ep.address](
815 boost::system::error_code const& ec) {
816 checkComplete(remoteAddress, checkedAddress, ec);
817 });
818
819 // Note that we simply discard the first Endpoint
820 // that the neighbor sends when we perform the
821 // listening test. They will just send us another
822 // one in a few seconds.
823
824 continue;
825 }
826
827 // If they failed the test then skip the address
828 if (!slot->canAccept)
829 continue;
830 }
831
832 // We only add to the livecache if the neighbor passed the
833 // listening test, else we silently drop neighbor endpoint
834 // since their listening port is misconfigured.
835 //
836 livecache.insert(ep);
837 bootcache.insert(ep.address);
838 }
839
840 slot->whenAcceptEndpoints = now + tuning::kSecondsPerMessage;
841 }
842
843 //--------------------------------------------------------------------------
844
845 void
846 remove(SlotImp::ptr const& slot)
847 {
848 {
849 auto const iter = slots.find(slot->remoteEndpoint());
850 // The slot must exist in the table
851 if (iter == slots.end())
852 {
854 "peer_finder::Logic::remove(): remote_endpoint "
855 "missing from slots_");
856 }
857
858 // Remove from slot by IP table
859 slots.erase(iter);
860 }
861 // Remove the key if present
862 if (slot->publicKey() != std::nullopt)
863 {
864 auto const iter = keys.find(*slot->publicKey());
865 // Key must exist
866 if (iter == keys.end())
867 {
869 "peer_finder::Logic::remove(): public_key missing "
870 "from keys_");
871 }
872
873 keys.erase(iter);
874 }
875 // Remove from connected address table
876 {
877 auto const iter(connectedAddresses.find(slot->remoteEndpoint().address()));
878 // Address must exist
879 if (iter == connectedAddresses.end())
880 {
882 "peer_finder::Logic::remove(): remote_endpoint "
883 "address missing from connectedAddresses_");
884 }
885
886 connectedAddresses.erase(iter);
887 }
888
889 // Update counts
890 counts_.remove(*slot);
891 }
892
893 void
895 {
896 std::scoped_lock const _(lock);
897
898 remove(slot);
899
900 beast::WrappedSink sink{journal.sink(), slot->prefix()};
901 beast::Journal const journal{sink};
902
903 // Mark fixed slot failure
904 if (slot->fixed() && !slot->inbound() && slot->state() != Slot::State::Active)
905 {
906 auto iter(fixed_.find(slot->remoteEndpoint()));
907 if (iter == fixed_.end())
908 {
910 "peer_finder::Logic::on_closed(): remote_endpoint "
911 "missing from fixed_");
912 }
913
914 iter->second.failure(clock.now());
915 JLOG(journal.debug()) << "Logic fixed failed";
916 }
917
918 // Do state specific bookkeeping
919 switch (slot->state())
920 {
922 JLOG(journal.trace()) << "Logic accept failed";
923 break;
924
927 bootcache.onFailure(slot->remoteEndpoint());
928 // VFALCO TODO If the address exists in the ephemeral/live
929 // endpoint livecache then we should mark the
930 // failure
931 // as if it didn't pass the listening test. We should also
932 // avoid propagating the address.
933 break;
934
936 JLOG(journal.trace()) << "Logic close";
937 break;
938
940 JLOG(journal.trace()) << "Logic finished";
941 break;
942
943 // LCOV_EXCL_START
944 default:
945 UNREACHABLE(
946 "xrpl::peer_finder::Logic::on_closed : invalid slot "
947 "state");
948 break;
949 // LCOV_EXCL_STOP
950 }
951 }
952
953 void
955 {
956 std::scoped_lock const _(lock);
957
958 bootcache.onFailure(slot->remoteEndpoint());
959 }
960
961 // Insert a set of redirect IP addresses into the Bootcache
962 template <class FwdIter>
963 void
964 onRedirects(FwdIter first, FwdIter last, boost::asio::ip::tcp::endpoint const& remoteAddress);
965
966 //--------------------------------------------------------------------------
967
968 // Returns `true` if the address matches a fixed slot address
969 // Must have the lock held
970 bool
971 fixed(beast::ip::Endpoint const& endpoint) const
972 {
973 return std::ranges::any_of(
974 fixed_, [&endpoint](auto const& entry) { return entry.first == endpoint; });
975 }
976
977 // Returns `true` if the address matches a fixed slot address
978 // Note that this does not use the port information in the ip::Endpoint
979 // Must have the lock held
980 bool
981 fixed(beast::ip::Address const& address) const
982 {
983 return std::ranges::any_of(
984 fixed_, [&address](auto const& entry) { return entry.first.address() == address; });
985 }
986
987 //--------------------------------------------------------------------------
988 //
989 // Connection Strategy
990 //
991 //--------------------------------------------------------------------------
992
996 template <class Container>
997 void
999 {
1000 auto const now(clock.now());
1001 for (auto iter = fixed_.begin(); needed && iter != fixed_.end(); ++iter)
1002 {
1003 auto const& address(iter->first.address());
1004 if (iter->second.when() <= now && squelches.find(address) == squelches.end() &&
1005 std::ranges::none_of(slots, [address](Slots::value_type const& v) {
1006 return address == v.first.address();
1007 }))
1008 {
1009 squelches.insert(iter->first.address());
1010 c.push_back(iter->first);
1011 --needed;
1012 }
1013 }
1014 }
1015
1016 //--------------------------------------------------------------------------
1017
1018 void
1020 {
1021 fetch(source);
1022 }
1023
1024 void
1026 {
1027 sources.push_back(source);
1028 }
1029
1030 //--------------------------------------------------------------------------
1031 //
1032 // Bootcache livecache sources
1033 //
1034 //--------------------------------------------------------------------------
1035
1036 // Add a set of addresses.
1037 // Returns the number of addresses added.
1038 //
1039 int
1041 {
1042 int count(0);
1043 std::scoped_lock const _(lock);
1044 for (auto const& addr : list)
1045 {
1046 if (bootcache.insertStatic(addr))
1047 ++count;
1048 }
1049 return count;
1050 }
1051
1052 // Fetch bootcache addresses from the specified source.
1053 void
1055 {
1056 Source::Results results;
1057
1058 {
1059 {
1060 std::scoped_lock const _(lock);
1061 if (stopping)
1062 return;
1063 fetchSource = source;
1064 }
1065
1066 // VFALCO NOTE The fetch is synchronous,
1067 // not sure if that's a good thing.
1068 //
1069 source->fetch(results, journal);
1070
1071 {
1072 std::scoped_lock const _(lock);
1073 if (stopping)
1074 return;
1075 fetchSource = nullptr;
1076 }
1077 }
1078
1079 if (!results.error)
1080 {
1081 int const count(addBootcacheAddresses(results.addresses));
1082 JLOG(journal.info()) << std::left << std::setw(18) << "Logic added " << count << " new "
1083 << ((count == 1) ? "address" : "addresses") << " from "
1084 << source->name();
1085 }
1086 else
1087 {
1088 JLOG(journal.error()) << std::left << std::setw(18) << "Logic failed "
1089 << "'" << source->name() << "' fetch, "
1090 << results.error.message();
1091 }
1092 }
1093
1094 //--------------------------------------------------------------------------
1095 //
1096 // Endpoint message handling
1097 //
1098 //--------------------------------------------------------------------------
1099
1100 // Returns true if the ip::Endpoint contains no invalid data.
1101 bool
1103 {
1104 if (isUnspecified(address))
1105 return false;
1106 if (!isPublic(address))
1107 return false;
1108 if (address.port() == 0)
1109 return false;
1110 return true;
1111 }
1112
1113 //--------------------------------------------------------------------------
1114 //
1115 // PropertyStream
1116 //
1117 //--------------------------------------------------------------------------
1118
1119 void
1121 {
1122 for (auto const& entry : slots)
1123 {
1125 SlotImp const& slot(*entry.second);
1126 if (slot.localEndpoint() != std::nullopt)
1127 item["local_address"] = to_string(*slot.localEndpoint());
1128 item["remote_address"] = to_string(slot.remoteEndpoint());
1129 if (slot.inbound())
1130 item["inbound"] = "yes";
1131 if (slot.fixed())
1132 item["fixed"] = "yes";
1133 if (slot.reserved())
1134 item["reserved"] = "yes";
1135
1136 item["state"] = stateString(slot.state());
1137 }
1138 }
1139
1140 void
1142 {
1143 std::scoped_lock const _(lock);
1144
1145 // VFALCO NOTE These ugly casts are needed because
1146 // of how std::size_t is declared on some linuxes
1147 //
1148 map["bootcache"] = std::uint32_t(bootcache.size());
1149 map["fixed"] = std::uint32_t(fixed_.size());
1150
1151 {
1152 beast::PropertyStream::Set child("peers", map);
1153 writeSlots(child, slots);
1154 }
1155
1156 {
1157 beast::PropertyStream::Map child("counts", map);
1158 counts_.onWrite(child);
1159 }
1160
1161 {
1162 beast::PropertyStream::Map child("config", map);
1163 config_.onWrite(child);
1164 }
1165
1166 {
1167 beast::PropertyStream::Map child("livecache", map);
1168 livecache.onWrite(child);
1169 }
1170
1171 {
1172 beast::PropertyStream::Map child("bootcache", map);
1173 bootcache.onWrite(child);
1174 }
1175 }
1176
1177 //--------------------------------------------------------------------------
1178 //
1179 // Diagnostics
1180 //
1181 //--------------------------------------------------------------------------
1182
1183 Counts const&
1184 counts() const
1185 {
1186 return counts_;
1187 }
1188
1189 static std::string
1191 {
1192 switch (state)
1193 {
1195 return "accept";
1197 return "connect";
1199 return "connected";
1201 return "active";
1203 return "closing";
1204 default:
1205 break;
1206 };
1207 return "?";
1208 }
1209};
1210
1211//------------------------------------------------------------------------------
1212
1213template <class Checker>
1214template <class FwdIter>
1215void
1217 FwdIter first,
1218 FwdIter last,
1219 boost::asio::ip::tcp::endpoint const& remoteAddress)
1220{
1221 std::scoped_lock const _(lock);
1222 std::size_t n = 0;
1223 for (; first != last && n < tuning::kMaxRedirects; ++first, ++n)
1225 if (n > 0)
1226 {
1227 JLOG(journal.trace()) << std::left << std::setw(18) << "Logic add " << n
1228 << " redirect IPs from " << remoteAddress;
1229 }
1230}
1231
1232} // namespace xrpl::peer_finder
T any_of(T... args)
T begin(T... args)
std::chrono::steady_clock::time_point time_point
A generic endpoint for log messages.
Definition Journal.h:44
Wraps a Journal::Sink to prefix its output with a string.
Definition WrappedSink.h:19
A version-independent IP address and port combination.
Definition IPEndpoint.h:24
Endpoint atPort(Port port) const
Returns a new Endpoint with a different port.
Definition IPEndpoint.h:65
Port port() const
Returns the port number on the endpoint.
Definition IPEndpoint.h:56
Address const & address() const
Returns the address portion of this endpoint.
Definition IPEndpoint.h:74
A public key.
Definition PublicKey.h:53
Stores IP addresses useful for gaining initial connections.
Definition Bootcache.h:36
Tests remote listening sockets to make sure they are connectable.
Definition Checker.h:21
Receives handouts for making automatic connections.
Definition Handouts.h:258
beast::aged_set< beast::ip::Address > Squelches
Definition Handouts.h:262
bool tryInsert(beast::ip::Endpoint const &endpoint)
Definition Handouts.h:319
Manages the count of available connections for the various slots.
Definition Counts.h:24
The Livecache holds the short-lived relayed Endpoint messages.
Definition Livecache.h:188
bool fixed(beast::ip::Endpoint const &endpoint) const
void onFailure(SlotImp::ptr const &slot)
std::vector< Endpoint > redirect(SlotImp::ptr const &slot)
Return a list of addresses suitable for redirection.
int addBootcacheAddresses(IPAddresses const &list)
std::vector< std::pair< std::shared_ptr< Slot >, std::vector< Endpoint > > > buildEndpointsForPeers()
Result activate(SlotImp::ptr const &slot, PublicKey const &key, bool reserved)
void preprocess(SlotImp::ptr const &slot, Endpoints &list)
void getFixed(std::size_t needed, Container &c, ConnectHandouts::Squelches &squelches)
Adds eligible Fixed addresses for outbound attempts.
std::vector< beast::ip::Endpoint > autoconnect()
Create new outbound connection attempts as needed.
void addFixedPeer(std::string_view name, std::vector< beast::ip::Endpoint > const &addresses)
void fetch(std::shared_ptr< Source > const &source)
std::multiset< beast::ip::Address > connectedAddresses
void onClosed(SlotImp::ptr const &slot)
static std::string stateString(Slot::State state)
bool fixed(beast::ip::Address const &address) const
void addSource(std::shared_ptr< Source > const &source)
std::map< beast::ip::Endpoint, Fixed > fixed_
void writeSlots(beast::PropertyStream::Set &set, Slots const &slots)
void checkComplete(beast::ip::Endpoint const &remoteAddress, beast::ip::Endpoint const &checkedAddress, boost::system::error_code ec)
std::pair< SlotImp::ptr, Result > newOutboundSlot(beast::ip::Endpoint const &remoteEndpoint)
std::pair< SlotImp::ptr, Result > newInboundSlot(beast::ip::Endpoint const &localEndpoint, beast::ip::Endpoint const &remoteEndpoint)
bool onConnected(SlotImp::ptr const &slot, beast::ip::Endpoint const &localEndpoint)
void onRedirects(FwdIter first, FwdIter last, boost::asio::ip::tcp::endpoint const &remoteAddress)
std::shared_ptr< Source > fetchSource
bool isValidAddress(beast::ip::Endpoint const &address)
void addStaticSource(std::shared_ptr< Source > const &source)
void remove(SlotImp::ptr const &slot)
void onWrite(beast::PropertyStream::Map &map)
void onEndpoints(SlotImp::ptr const &slot, Endpoints list)
void addFixedPeer(std::string_view name, beast::ip::Endpoint const &ep)
Logic(clock_type &clock, Store &store, Checker &checker, beast::Journal journal)
clock_type::time_point whenBroadcast
std::map< beast::ip::Endpoint, std::shared_ptr< SlotImp > > Slots
std::vector< std::shared_ptr< Source > > sources
ConnectHandouts::Squelches squelches
Receives handouts for redirecting a connection.
Definition Handouts.h:86
std::vector< Endpoint > & list()
Definition Handouts.h:108
std::shared_ptr< SlotImp > ptr
Definition SlotImp.h:20
std::optional< beast::ip::Endpoint > const & localEndpoint() const override
The local endpoint of the socket, when known.
Definition SlotImp.h:63
State state() const override
Returns the state of the connection.
Definition SlotImp.h:51
beast::ip::Endpoint const & remoteEndpoint() const override
The remote endpoint of socket.
Definition SlotImp.h:57
void setListeningPort(std::uint16_t port)
Definition SlotImp.h:90
std::string prefix() const
Definition SlotImp.h:75
bool reserved() const override
Returns true if this is a reserved connection.
Definition SlotImp.h:45
bool fixed() const override
Returns true if this is a fixed connection.
Definition SlotImp.h:39
bool inbound() const override
Returns true if this is an inbound connection.
Definition SlotImp.h:33
Abstract persistence for PeerFinder data.
Definition Store.h:15
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T for_each(T... args)
T forward_as_tuple(T... args)
T left(T... args)
T make_shared(T... args)
T make_tuple(T... args)
boost::asio::ip::address Address
Definition IPAddress.h:20
boost::asio::ip::address_v6 AddressV6
Definition IPAddressV6.h:7
std::size_t expire(AgedContainer &c, std::chrono::duration< Rep, Period > const &age)
Expire aged container items past the specified age.
constexpr std::chrono::seconds kRecentAttemptDuration(60)
constexpr std::chrono::seconds kSecondsPerMessage(151)
static constexpr auto kMaxRedirects
Max redirects we will accept from one connection.
std::vector< beast::ip::Endpoint > IPAddresses
Represents a set of addresses.
Result
Possible results from activating a slot.
std::vector< Endpoint > Endpoints
A set of Endpoint used for connecting.
beast::AbstractClock< std::chrono::steady_clock > clock_type
void handout(TargetFwdIter first, TargetFwdIter last, SeqFwdIter seqFirst, SeqFwdIter seqLast)
Distributes objects to targets according to business rules.
Definition Handouts.h:53
std::string_view to_string(Result result) noexcept
Converts a Result enum value to its string representation.
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
void logicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
beast::xor_shift_engine & defaultPrng()
Return the default random engine.
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T piecewise_construct
T shuffle(T... args)
T ref(T... args)
T reserve(T... args)
T resize(T... args)
T setw(T... args)
T size(T... args)
static ip::Endpoint fromAsio(boost::asio::ip::address const &address)
PeerFinder configuration settings.
Describes a connectable peer address along with some metadata.
beast::ip::Endpoint address
The results of a fetch.
Definition Source.h:28
boost::system::error_code error
Definition Source.h:32