feat: Add verify_endpoints to help local peer network development (#7268)

Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com>
This commit is contained in:
Jingchen
2026-05-14 18:07:08 +01:00
committed by GitHub
parent afbccf971a
commit cce4cfef10
12 changed files with 124 additions and 27 deletions

View File

@@ -527,6 +527,17 @@
#
# The current default (which is subject to change) is 300 seconds.
#
# verify_endpoints = <0 | 1>
#
# If set to 0, the server will skip validation of endpoint
# addresses received in TMEndpoints peer protocol messages,
# allowing addresses that are not publicly routable or have a
# port of 0. The default is 1 (verification enabled).
#
# WARNING: Disabling this option is a security risk and should
# only be used for local testing and debugging. Do not disable
# on mainnet.
#
#
# [transaction_queue] EXPERIMENTAL
#

View File

@@ -326,3 +326,4 @@ words:
- xrplf
- xxhash
- xxhasher
- CGNAT

View File

@@ -14,7 +14,45 @@ isPrivate(AddressV4 const& addr)
bool
isPublic(AddressV4 const& addr)
{
return !isPrivate(addr) && !addr.is_multicast();
if (isPrivate(addr))
return false;
if (addr.is_multicast())
return false;
auto const ip = addr.to_uint();
// 0.0.0.0/8 "This network"
if ((ip & 0xff000000) == 0x00000000)
return false;
// 100.64.0.0/10 Shared Address Space (CGNAT) - RFC 6598
if ((ip & 0xffc00000) == 0x64400000)
return false;
// 169.254.0.0/16 Link-local
if ((ip & 0xffff0000) == 0xa9fe0000)
return false;
// 192.0.0.0/24 IETF Protocol Assignments - RFC 6890
if ((ip & 0xffffff00) == 0xc0000000)
return false;
// 192.0.2.0/24 TEST-NET-1 (documentation) - RFC 5737
if ((ip & 0xffffff00) == 0xc0000200)
return false;
// 192.88.99.0/24 6to4 Relay Anycast (deprecated) - RFC 7526
if ((ip & 0xffffff00) == 0xc0586300)
return false;
// 198.18.0.0/15 Benchmarking - RFC 2544
if ((ip & 0xfffe0000) == 0xc6120000)
return false;
// 198.51.100.0/24 TEST-NET-2 (documentation) - RFC 5737
if ((ip & 0xffffff00) == 0xc6336400)
return false;
// 203.0.113.0/24 TEST-NET-3 (documentation) - RFC 5737
if ((ip & 0xffffff00) == 0xcb007100)
return false;
// 240.0.0.0/4 Reserved for future use - RFC 1112
if ((ip & 0xf0000000) == 0xf0000000)
return false;
return true;
}
char

View File

@@ -9,17 +9,53 @@ namespace beast::IP {
bool
isPrivate(AddressV6 const& addr)
{
return (
((addr.to_bytes()[0] & 0xfd) != 0) || // TODO fc00::/8 too ?
(addr.is_v4_mapped() &&
isPrivate(boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, addr))));
// fc00::/7 - Unique Local Address (ULA), covers fc00:: and fd00::
if ((addr.to_bytes()[0] & 0xfe) == 0xfc)
return true;
if (addr.is_v4_mapped())
return isPrivate(boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, addr));
return false;
}
bool
isPublic(AddressV6 const& addr)
{
// TODO is this correct?
return !isPrivate(addr) && !addr.is_multicast();
if (addr.is_loopback())
return false;
if (addr.is_v4_mapped())
return isPublic(boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, addr));
if (isPrivate(addr))
return false;
if (addr.is_multicast())
return false;
if (addr.is_unspecified())
return false;
auto const b = addr.to_bytes();
// fe80::/10 - Link-local
if (b[0] == 0xfe && (b[1] & 0xc0) == 0x80)
return false;
// 100::/64 - Discard prefix (RFC 6666)
if (b[0] == 0x01 && b[1] == 0x00 && b[2] == 0 && b[3] == 0 && b[4] == 0 && b[5] == 0 &&
b[6] == 0 && b[7] == 0)
return false;
// 2001:db8::/32 - Documentation (RFC 3849)
if (b[0] == 0x20 && b[1] == 0x01 && b[2] == 0x0d && b[3] == 0xb8)
return false;
// 2001::/32 - IETF Protocol Assignments / Teredo (RFC 4380)
if (b[0] == 0x20 && b[1] == 0x01 && b[2] == 0x00 && b[3] == 0x00)
return false;
// 2001:20::/28 - ORCHIDv2 (RFC 7343)
// 28-bit prefix: 0x2001002 => b[0]=0x20, b[1]=0x01, b[2]=0x00,
// top nibble of b[3]=0x2
if (b[0] == 0x20 && b[1] == 0x01 && b[2] == 0x00 && (b[3] & 0xf0) == 0x20)
return false;
// 2002::/16 - 6to4 (RFC 3056, deprecated by RFC 7526)
if (b[0] == 0x20 && b[1] == 0x02)
return false;
return true;
}
} // namespace beast::IP

View File

@@ -424,7 +424,7 @@ public:
(c.PEERS_MAX == max && c.PEERS_IN_MAX == 0 && c.PEERS_OUT_MAX == 0) ||
(c.PEERS_IN_MAX == *maxIn && c.PEERS_OUT_MAX == *maxOut));
Config const config = Config::makeConfig(c, port, false, 0);
Config const config = Config::makeConfig(c, port, false, 0, true);
Counts counts;
counts.onConfig(config);

View File

@@ -1357,7 +1357,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
// if (!config_.standalone())
overlay_ = makeOverlay(
*this,
setupOverlay(*config_),
setupOverlay(*config_, journal_),
*serverHandler_,
*resourceManager_,
*resolver_,

View File

@@ -47,6 +47,7 @@ public:
std::uint32_t crawlOptions = 0;
std::optional<std::uint32_t> networkID;
bool vlEnabled = true;
bool verifyEndpoints = true;
};
using PeerSequence = std::vector<std::shared_ptr<Peer>>;

View File

@@ -508,7 +508,8 @@ OverlayImpl::start()
app_.config(),
serverHandler_.setup().overlay.port(),
app_.getValidationPublicKey().has_value(),
setup_.ipLimit);
setup_.ipLimit,
setup_.verifyEndpoints);
peerFinder_->setConfig(config);
peerFinder_->start();
@@ -1510,7 +1511,7 @@ OverlayImpl::deleteIdlePeers()
//------------------------------------------------------------------------------
Overlay::Setup
setupOverlay(BasicConfig const& config)
setupOverlay(BasicConfig const& config, beast::Journal j)
{
Overlay::Setup setup;
@@ -1528,9 +1529,17 @@ setupOverlay(BasicConfig const& config)
{
boost::system::error_code ec;
setup.publicIp = boost::asio::ip::make_address(ip, ec);
if (ec || beast::IP::isPrivate(setup.publicIp))
if (ec || !beast::IP::isPublic(setup.publicIp))
Throw<std::runtime_error>("Configured public IP is invalid");
}
set(setup.verifyEndpoints, true, "verify_endpoints", section);
if (!setup.verifyEndpoints)
{
JLOG(j.warn()) << "Endpoint verification is disabled. This is a "
"security risk and should only be used for "
"testing.";
}
}
{

View File

@@ -10,7 +10,7 @@
namespace xrpl {
Overlay::Setup
setupOverlay(BasicConfig const& config);
setupOverlay(BasicConfig const& config, beast::Journal j);
/** Creates the implementation of Overlay. */
std::unique_ptr<Overlay>

View File

@@ -59,6 +59,9 @@ struct Config
/** Limit how many incoming connections we allow per IP */
int ipLimit{0};
/** `true` if we want to verify endpoints in TMEndpoints messages */
bool verifyEndpoints = true;
//--------------------------------------------------------------------------
/** Create a configuration with default values. */
@@ -81,6 +84,8 @@ struct Config
* @param port server's listening port
* @param validationPublicKey true if validation public key is not empty
* @param ipLimit limit of incoming connections per IP
* @param verifyEndpoints `true` if we want to verify endpoints in
* TMEndpoints messages
* @return PeerFinder::Config
*/
static Config
@@ -88,10 +93,11 @@ struct Config
xrpl::Config const& config,
std::uint16_t port,
bool validationPublicKey,
int ipLimit);
int ipLimit,
bool verifyEndpoints);
friend bool
operator==(Config const& lhs, Config const& rhs);
operator==(Config const& lhs, Config const& rhs) = default;
};
//------------------------------------------------------------------------------

View File

@@ -696,7 +696,7 @@ public:
}
// Discard invalid addresses
if (!isValidAddress(ep.address))
if (config_.verifyEndpoints && !isValidAddress(ep.address))
{
JLOG(journal.debug())
<< beast::Leftw(18) << "Endpoints drop " << ep.address << " as invalid";
@@ -1088,6 +1088,8 @@ public:
{
if (isUnspecified(address))
return false;
if (isLoopback(address))
return false;
if (!isPublic(address))
return false;
if (address.port() == 0)

View File

@@ -15,16 +15,6 @@ Config::Config() : outPeers(calcOutPeers())
{
}
bool
operator==(Config const& lhs, Config const& rhs)
{
return lhs.autoConnect == rhs.autoConnect && lhs.peerPrivate == rhs.peerPrivate &&
lhs.wantIncoming == rhs.wantIncoming && lhs.inPeers == rhs.inPeers &&
lhs.maxPeers == rhs.maxPeers && lhs.outPeers == rhs.outPeers &&
lhs.features == rhs.features && lhs.ipLimit == rhs.ipLimit &&
lhs.listeningPort == rhs.listeningPort;
}
std::size_t
Config::calcOutPeers() const
{
@@ -61,6 +51,7 @@ Config::onWrite(beast::PropertyStream::Map& map) const
map["port"] = listeningPort;
map["features"] = features;
map["ip_limit"] = ipLimit;
map["verify_endpoints"] = verifyEndpoints;
}
Config
@@ -68,7 +59,8 @@ Config::makeConfig(
xrpl::Config const& cfg,
std::uint16_t port,
bool validationPublicKey,
int ipLimit)
int ipLimit,
bool verifyEndpoints)
{
PeerFinder::Config config;
@@ -121,6 +113,7 @@ Config::makeConfig(
config.listeningPort = port;
config.features = "";
config.ipLimit = ipLimit;
config.verifyEndpoints = verifyEndpoints;
// Enforce business rules
config.applyTuning();