diff --git a/doc/rippled-example.cfg b/doc/rippled-example.cfg index 41bfa51cf..db0280a8d 100644 --- a/doc/rippled-example.cfg +++ b/doc/rippled-example.cfg @@ -373,6 +373,15 @@ # Peers will use this information to reject attempt to proxy # connections to or from this server. # +# ip_limit = +# +# The maximum number of incoming peer connections allowed by a single +# IP that isn't classified as "private" in RFC1918. The implementation +# imposes some hard and soft upper limits on this value to prevent a +# single host from consuming all inbound slots. If the value is not +# present the server will autoconfigure an appropriate limit. +# +# # # [transaction_queue] EXPERIMENTAL # diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index f71868e94..64392fed3 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -208,7 +208,7 @@ public: // Peer networking parameters bool PEER_PRIVATE = false; // True to ask peers not to relay current IP. - unsigned int PEERS_MAX = 0; + int PEERS_MAX = 0; int WEBSOCKET_PING_FREQ = 5 * 60; diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 8735344f8..cb98cacc9 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -371,11 +371,12 @@ void Config::loadFromString (std::string const& fileContents) (void) getSingleSection (secConfig, SECTION_VALIDATORS_SITE, VALIDATORS_SITE, j_); std::string strTemp; + if (getSingleSection (secConfig, SECTION_PEER_PRIVATE, strTemp, j_)) PEER_PRIVATE = beast::lexicalCastThrow (strTemp); if (getSingleSection (secConfig, SECTION_PEERS_MAX, strTemp, j_)) - PEERS_MAX = beast::lexicalCastThrow (strTemp); + PEERS_MAX = std::max (0, beast::lexicalCastThrow (strTemp)); if (getSingleSection (secConfig, SECTION_NODE_SIZE, strTemp, j_)) { diff --git a/src/ripple/overlay/Overlay.h b/src/ripple/overlay/Overlay.h index bfb7564b5..673079999 100644 --- a/src/ripple/overlay/Overlay.h +++ b/src/ripple/overlay/Overlay.h @@ -70,6 +70,7 @@ public: std::shared_ptr context; bool expire = false; beast::IP::Address public_ip; + int ipLimit = 0; }; using PeerSequence = std::vector ; diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index 803d7339f..d0aac43fd 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -506,6 +506,7 @@ OverlayImpl::onPrepare() !app_.config().PEER_PRIVATE; config.listeningPort = port; config.features = ""; + config.ipLimit = setup_.ipLimit; // Enforce business rules config.applyTuning(); @@ -1056,6 +1057,10 @@ setup_Overlay (BasicConfig const& config) setup.context = make_SSLContext(); setup.expire = get(section, "expire", false); + set (setup.ipLimit, "ip_limit", section); + if (setup.ipLimit < 0) + throw std::runtime_error ("Configured IP limit is invalid"); + std::string ip; set (ip, "public_ip", section); if (! ip.empty ()) diff --git a/src/ripple/peerfinder/PeerfinderManager.h b/src/ripple/peerfinder/PeerfinderManager.h index df72df8f6..261cdcd5c 100644 --- a/src/ripple/peerfinder/PeerfinderManager.h +++ b/src/ripple/peerfinder/PeerfinderManager.h @@ -73,6 +73,9 @@ struct Config /** The set of features we advertise. */ std::string features; + /** Limit how many incoming connections we allow per IP */ + int ipLimit; + //-------------------------------------------------------------------------- /** Create a configuration with default values. */ diff --git a/src/ripple/peerfinder/impl/Logic.h b/src/ripple/peerfinder/impl/Logic.h index 80a2255ee..5524b00be 100644 --- a/src/ripple/peerfinder/impl/Logic.h +++ b/src/ripple/peerfinder/impl/Logic.h @@ -263,13 +263,13 @@ public: // Check for duplicate connection if (is_public (remote_endpoint)) { - auto const iter = connectedAddresses_.find ( + auto const count = connectedAddresses_.count ( remote_endpoint.address()); - if (iter != connectedAddresses_.end()) + if (count > config_.ipLimit) { if (m_journal.debug) m_journal.debug << beast::leftw (18) << "Logic dropping inbound " << remote_endpoint << - " as duplicate"; + " because of ip limits."; return SlotImp::ptr(); } } diff --git a/src/ripple/peerfinder/impl/PeerfinderConfig.cpp b/src/ripple/peerfinder/impl/PeerfinderConfig.cpp index 7831cd04c..d05aafb00 100644 --- a/src/ripple/peerfinder/impl/PeerfinderConfig.cpp +++ b/src/ripple/peerfinder/impl/PeerfinderConfig.cpp @@ -30,6 +30,7 @@ Config::Config() , wantIncoming (true) , autoConnect (true) , listeningPort (0) + , ipLimit (0) { } @@ -45,6 +46,25 @@ void Config::applyTuning () if (maxPeers < Tuning::minOutCount) maxPeers = Tuning::minOutCount; outPeers = calcOutPeers (); + + auto const inPeers = maxPeers - outPeers; + + if (ipLimit == 0) + { + // Unless a limit is explicitly set, we allow between + // 2 and 5 connections from non RFC-1918 "private" + // IP addresses. + ipLimit = 2; + + if (inPeers > Tuning::defaultMaxPeers) + ipLimit += std::min(5, + static_cast(inPeers / Tuning::defaultMaxPeers)); + } + + // We don't allow a single IP to consume all incoming slots, + // unless we only have one incoming slot available. + ipLimit = std::max(1, + std::min(ipLimit, static_cast(inPeers / 2))); } void Config::onWrite (beast::PropertyStream::Map &map) @@ -55,6 +75,7 @@ void Config::onWrite (beast::PropertyStream::Map &map) map ["auto_connect"] = autoConnect; map ["port"] = listeningPort; map ["features"] = features; + map ["ip_limit"] = ipLimit; } }