diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index 45269903b2..5de61bb6d1 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -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 # diff --git a/cspell.config.yaml b/cspell.config.yaml index 34482d9d8f..bc56ef5d79 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -326,3 +326,4 @@ words: - xrplf - xxhash - xxhasher + - CGNAT diff --git a/src/libxrpl/beast/net/IPAddressV4.cpp b/src/libxrpl/beast/net/IPAddressV4.cpp index 48554d0ec3..e087da92eb 100644 --- a/src/libxrpl/beast/net/IPAddressV4.cpp +++ b/src/libxrpl/beast/net/IPAddressV4.cpp @@ -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 diff --git a/src/libxrpl/beast/net/IPAddressV6.cpp b/src/libxrpl/beast/net/IPAddressV6.cpp index fad11dddc0..c75ccaf1cc 100644 --- a/src/libxrpl/beast/net/IPAddressV6.cpp +++ b/src/libxrpl/beast/net/IPAddressV6.cpp @@ -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 diff --git a/src/test/peerfinder/PeerFinder_test.cpp b/src/test/peerfinder/PeerFinder_test.cpp index 17ce6835ef..2e4ab001b4 100644 --- a/src/test/peerfinder/PeerFinder_test.cpp +++ b/src/test/peerfinder/PeerFinder_test.cpp @@ -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); diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index f7679d14d8..20a81f6768 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -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_, diff --git a/src/xrpld/overlay/Overlay.h b/src/xrpld/overlay/Overlay.h index 80430293d8..ef97ea7f24 100644 --- a/src/xrpld/overlay/Overlay.h +++ b/src/xrpld/overlay/Overlay.h @@ -47,6 +47,7 @@ public: std::uint32_t crawlOptions = 0; std::optional networkID; bool vlEnabled = true; + bool verifyEndpoints = true; }; using PeerSequence = std::vector>; diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index bfed850e5c..770df6e43a 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -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("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."; + } } { diff --git a/src/xrpld/overlay/make_Overlay.h b/src/xrpld/overlay/make_Overlay.h index 94d1110f4b..acd477deec 100644 --- a/src/xrpld/overlay/make_Overlay.h +++ b/src/xrpld/overlay/make_Overlay.h @@ -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 diff --git a/src/xrpld/peerfinder/PeerfinderManager.h b/src/xrpld/peerfinder/PeerfinderManager.h index 5b235a4db1..083abf92c6 100644 --- a/src/xrpld/peerfinder/PeerfinderManager.h +++ b/src/xrpld/peerfinder/PeerfinderManager.h @@ -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; }; //------------------------------------------------------------------------------ diff --git a/src/xrpld/peerfinder/detail/Logic.h b/src/xrpld/peerfinder/detail/Logic.h index fce5bb4afa..4d0cc9cf29 100644 --- a/src/xrpld/peerfinder/detail/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -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) diff --git a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp index 83b78883db..a8323f6239 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp @@ -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();