Add an option to allow disabling endpoint verification after tightening the requirements

This commit is contained in:
JCW
2026-04-09 14:51:42 +01:00
parent 98c361608a
commit 4146596ebc
7 changed files with 106 additions and 17 deletions

View File

@@ -527,6 +527,13 @@
#
# The current default (which is subject to change) is 300 seconds.
#
# verify_endpoints = <0 | 1>
#
# If set to 1, the server will verify the validity of endpoint
# addresses received in TMEndpoints peer protocol messages by
# checking that they are well-formed and publicly routable. Invalid
# endpoints will be dropped. The default is 1 (verification enabled).
#
#
# [transaction_queue] EXPERIMENTAL
#

View File

@@ -52,7 +52,7 @@ public:
async_connect(beast::IP::Endpoint const& ep, Handler&& handler)
{
boost::system::error_code ec;
handler(ep, ep, ec);
handler(ec);
}
};
@@ -541,6 +541,84 @@ public:
pass("[2606:4700:4700::1111]:8080");
}
void
test_verify_endpoints()
{
// Helper that sets up a Logic instance, creates and activates a slot,
// then calls on_endpoints with the given list and returns the
// livecache size afterwards.
auto run = [&](bool verifyEndpoints, Endpoints eps) -> std::size_t {
TestStore store;
TestChecker checker;
TestStopwatch clock;
Logic<TestChecker> logic(clock, store, checker, journal_);
{
Config c;
c.autoConnect = false;
c.listeningPort = 1024;
c.ipLimit = 2;
c.verifyEndpoints = verifyEndpoints;
logic.config(c);
}
auto const remote = beast::IP::Endpoint::from_string("65.0.0.1:5");
auto const local =
beast::IP::Endpoint::from_string("65.0.0.2:1024");
auto const [slot, r] = logic.new_outbound_slot(remote);
BEAST_EXPECT(slot != nullptr);
BEAST_EXPECT(r == Result::success);
BEAST_EXPECT(logic.onConnected(slot, local));
PublicKey const pk(randomKeyPair(KeyType::secp256k1).first);
BEAST_EXPECT(logic.activate(slot, pk, false) == Result::success);
logic.on_endpoints(slot, std::move(eps));
auto const size = logic.livecache_.size();
logic.on_closed(slot);
return size;
};
{
testcase("verify_endpoints enabled");
// Valid public addresses
Endpoints eps;
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("44.0.0.1:5"), 1});
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("44.0.0.2:6"), 1});
// Invalid: private address
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("10.0.0.1:5"), 1});
// Invalid: port 0
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("44.0.0.3:0"), 1});
// With verification enabled, only the 2 valid endpoints survive
BEAST_EXPECT(run(true, eps) == 2);
}
{
testcase("verify_endpoints disabled");
Endpoints eps;
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("44.0.0.1:5"), 1});
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("44.0.0.2:6"), 1});
// Private address — kept when verification is off
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("10.0.0.1:5"), 1});
// Port 0 — kept when verification is off
eps.push_back(
Endpoint{beast::IP::Endpoint::from_string("44.0.0.3:0"), 1});
// Without verification, all 4 endpoints survive
BEAST_EXPECT(run(false, eps) == 4);
}
}
void
test_onConnected_self_connection()
{
@@ -597,7 +675,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 config = Config::makeConfig(c, port, false, 0, true);
Counts counts;
counts.onConfig(config);
@@ -698,6 +776,7 @@ public:
test_addFixedPeer_no_port();
test_onConnected_self_connection();
test_is_valid_address();
test_verify_endpoints();
}
};

View File

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

View File

@@ -447,7 +447,8 @@ OverlayImpl::start()
app_.config(),
serverHandler_.setup().overlay.port(),
app_.getValidationPublicKey().has_value(),
setup_.ipLimit);
setup_.ipLimit,
setup_.verifyEndpoints);
m_peerFinder->setConfig(config);
m_peerFinder->start();
@@ -1469,6 +1470,8 @@ setup_Overlay(BasicConfig const& config)
if (ec || beast::IP::is_private(setup.public_ip))
Throw<std::runtime_error>("Configured public IP is invalid");
}
set(setup.verifyEndpoints, true, "verify_endpoints", section);
}
{

View File

@@ -61,6 +61,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 = false;
//--------------------------------------------------------------------------
/** Create a configuration with default values. */
@@ -83,6 +86,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
@@ -90,10 +95,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

@@ -693,7 +693,7 @@ public:
}
// Discard invalid addresses
if (!is_valid_address(ep.address))
if (!is_valid_address(ep.address) && config_.verifyEndpoints)
{
JLOG(m_journal.debug())
<< beast::leftw(18) << "Endpoints drop " << ep.address << " as invalid";

View File

@@ -11,16 +11,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
{
@@ -56,6 +46,7 @@ Config::onWrite(beast::PropertyStream::Map& map) const
map["port"] = listeningPort;
map["features"] = features;
map["ip_limit"] = ipLimit;
map["verify_endpoints"] = verifyEndpoints;
}
Config
@@ -63,7 +54,8 @@ Config::makeConfig(
xrpl::Config const& cfg,
std::uint16_t port,
bool validationPublicKey,
int ipLimit)
int ipLimit,
bool verifyEndpoints)
{
PeerFinder::Config config;
@@ -116,6 +108,7 @@ Config::makeConfig(
config.listeningPort = port;
config.features = "";
config.ipLimit = ipLimit;
config.verifyEndpoints = verifyEndpoints;
// Enforce business rules
config.applyTuning();