//------------------------------------------------------------------------------ /* This file is part of rippled: https://github.com/ripple/rippled Copyright (c) 2012, 2013 Ripple Labs Inc. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== /* PeerFinder ---------- Implements the logic for announcing and discovering IP addresses for for connecting into the Ripple network. Introduction ------------ Each Peer (a computer running rippled) on the Ripple network requires a certain number of connections to other peers. These connections form an "overlay network." When a new peer wants to join the network, they need a robust source of network addresses (IP adresses) in order to establish outgoing connections. Once they have joined the network, they need a method of announcing their availaibility of accepting incoming connections. The Ripple network, like all peer to peer networks, defines a "directed graph" where each node represents a computer running the rippled software, and each vertex indicates a network connection. The direction of the connection tells us whether it is an outbound or inbound connection (from the perspective of a particular node). Fact #1: The total inbound and outbound connections of any overlay must be equal. This follows that for each node that has an established outbound connection, there must exist another node that has received the corresponding inbound connection. When a new peer joins the network it may or may not wish to receive inbound connections. Some peers are unable to accept incoming connections for various. For security reasons they may be behind a firewall that blocks accept requests. The administers may decide they don't want the connection traffic. Or they may wish to connect only to specific peers. Or they may simply be misconfigured. If a peer decides that it wishes to receive incoming connections, it needs a method to announce its IP address and port number, the features that it offers (for example, that it also services client requests), and the number of available connection slots. This is to handle the case where the peer reaches its desired number of peer connections, but may still want to inform the network that it will service clients. It may also be desired to indicate the number of free client slots. Pong ---- Once a peer is connected to the network we need a way both to inform our neighbors of our status with respect to accepting connections, and also to learn about new fresh addresses to connect to. For this we will define the "Pong" message. "Connection Strategy" --------------------- This is the overall strategy a peer uses to maintain its position in the Ripple network graph We define these values: peerCount (calculated) The number of currently connected and established peers outCount (calculated) The number of peers in PeerCount that are outbound connections. MinOutCount (hard-coded constant) The minimum number of OutCount we want. This also puts a floor on PeerCount. This protects against sybil attacks and makes sure that ledgers can get retrieved reliably. 10 is the proposed value. MaxPeerCount (a constant set in the rippled.cfg) The maximum number of peer connections, inbound or outbound, that a peer wishes to maintain. Setting MaxPeerCount equal to or below MinOutCount would disallow incoming connections. OutPercent (a baked-in program constant for now) The peer's target value for OutCount. When the value of OutCount is below this number, the peer will employ the Outgoing Strategy to raise its value of OutCount. This value is initially a constant in the program, defined by the developers. However, it may be changed through the consensus process. 15% is a proposed value. However, lets consider the case where OutDesired is exactly equal to MaxPeerCount / 2. In this case, a stable state will be reached when every peer is full, and has exactly the same number of inbound and outbound connections. The problem here is that there are now no available incoming connection slots. No new peers can enter the network. Lets consider the case where OutDesired is exactly equal to (MaxPeerCount / 2) - 1. The stable state for this network (assuming all peers can accept incoming) will leave us with network degree equal to MaxPeerCount - 2, with all peers having two available incoming connection slots. The global number of incoming connection slots will be equal to twice the number of nodes on the network. While this might seem to be a desirable outcome, note that the connectedness (degree of the overlay) plays a large part in determining the levels of traffic and ability to receive validations from desired nodes. Having every node with available incoming connections also means that entries in pong caches will continually fall out with new values and information will become less useful. For this reason, we advise that the value of OutDesired be fractional. Upon startup, a node will use its node ID (its 160 bit unique ID) to decide whether to round the value of OutDesired up or down. Using this method, we can precisely control the global number of available incoming connection slots. "Outgoing Strategy" ------------------- This is the method a peer uses to establish outgoing connections into the Ripple network. A peer whose PeerCount is zero will use these steps: 1. Attempt addresses from a local database of addresses 2. Attempt addresses from a set of "well known" domains in rippled.cfg This is the method used by a peer that is already connected to the Ripple network, to adjust the number of outgoing connections it is maintaining. "Incoming Strategy" ------------------------------ This is the method used by a peer to announce its ability and desire to receive incoming connections both for the purpose of obtaining additional peer connections and also for receiving requests from clients. Terms Overlay Network http://en.wikipedia.org/wiki/Overlay_network Directed Graph http://en.wikipedia.org/wiki/Directed_graph References: Gnutella 0.6 Protocol 2.2.2 Ping (0x00) 2.2.3 Pong (0x01) 2.2.4 Use of Ping and Pong messages 2.2.4.1 A simple pong caching scheme 2.2.4.2 Other pong caching schemes http://rfc-gnutella.sourceforge.net/src/rfc-0_6-draft.html Revised Gnutella Ping Pong Scheme By Christopher Rohrs and Vincent Falco http://rfc-gnutella.sourceforge.net/src/pong-caching.html */ namespace ripple { namespace PeerFinder { class ManagerImp : public Manager , public Thread , public DeadlineTimer::Listener , public LeakChecked { public: ServiceQueue m_queue; Journal m_journal; StoreSqdb m_store; CheckerAdapter m_checker; Logic m_logic; DeadlineTimer m_connectTimer; DeadlineTimer m_endpointsTimer; //-------------------------------------------------------------------------- ManagerImp (Stoppable& stoppable, Callback& callback, Journal journal) : Manager (stoppable) , Thread ("PeerFinder") , m_journal (journal) , m_store (journal) , m_checker (m_queue) , m_logic (callback, m_store, m_checker, journal) , m_connectTimer (this) , m_endpointsTimer (this) { #if 1 #if BEAST_MSVC if (beast_isRunningUnderDebugger()) { m_journal.sink().set_console (true); m_journal.sink().set_severity (Journal::kLowestSeverity); } #endif #endif } ~ManagerImp () { } //-------------------------------------------------------------------------- // // PeerFinder // void setConfig (Config const& config) { m_queue.dispatch (bind (&Logic::setConfig, &m_logic, config)); } void addStrings (std::string const& name, std::vector const& strings) { m_queue.dispatch (bind (&Logic::addStaticSource, &m_logic, SourceStrings::New (name, strings))); } void addURL (std::string const& name, std::string const& url) { } void onPeerConnected (PeerID const& id, IPEndpoint const& address, bool incoming) { m_queue.dispatch (bind (&Logic::onPeerConnected, &m_logic, id, address, incoming)); } void onPeerDisconnected (const PeerID& id) { m_queue.dispatch (bind (&Logic::onPeerDisconnected, &m_logic, id)); } void onPeerLegacyEndpoint (IPEndpoint const& ep) { m_queue.dispatch (bind (&Logic::onPeerLegacyEndpoint, &m_logic, ep)); } void onPeerEndpoints (PeerID const& id, std::vector const& endpoints) { m_queue.dispatch (beast::bind (&Logic::onPeerEndpoints, &m_logic, id, endpoints)); } //-------------------------------------------------------------------------- // // Stoppable // void onPrepare () { } void onStart () { startThread(); } void onStop () { m_checker.cancel (); if (this->Thread::isThreadRunning ()) { m_journal.debug << "Stopping"; m_connectTimer.cancel(); m_endpointsTimer.cancel(); m_queue.dispatch (bind (&Thread::signalThreadShouldExit, this)); } else { stopped(); } } //-------------------------------------------------------------------------- void onDeadlineTimer (DeadlineTimer& timer) { if (timer == m_connectTimer) { m_queue.dispatch (bind (&Logic::makeOutgoingConnections, &m_logic)); m_connectTimer.setExpiration (secondsPerConnect); } else if (timer == m_endpointsTimer) { m_queue.dispatch (bind (&Logic::sendEndpoints, &m_logic)); m_endpointsTimer.setExpiration (secondsPerEndpoints); } } void init () { m_journal.debug << "Initializing"; File const file (File::getSpecialLocation ( File::userDocumentsDirectory).getChildFile ("PeerFinder.sqlite")); m_journal.debug << "Opening database at '" << file.getFullPathName() << "'"; Error error (m_store.open (file)); if (error) { m_journal.fatal << "Failed to open '" << file.getFullPathName() << "'"; } if (! error) { m_logic.load (); } m_connectTimer.setExpiration (secondsPerConnect); m_endpointsTimer.setExpiration (secondsPerEndpoints); m_queue.post (bind (&Logic::makeOutgoingConnections, &m_logic)); } void run () { m_journal.debug << "Started"; init (); while (! this->threadShouldExit()) { m_queue.run_one(); } stopped(); } }; //------------------------------------------------------------------------------ Manager::Manager (Stoppable& parent) : Stoppable ("PeerFinder", parent) { } Manager* Manager::New (Stoppable& parent, Callback& callback, Journal journal) { return new ManagerImp (parent, callback, journal); } } }