diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index ab676a0244..67baf6ffcd 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -351,6 +351,14 @@ # connection is no longer available. # # +# [server_domain] +# +# domain name +# +# The domain under which a TOML file applicable to this server can be +# found. A server may lie about its domain so the TOML should contain +# a reference to this server by pubkey in the [nodes] array. +# # #------------------------------------------------------------------------------- # diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 113e5276e0..bc4bdd9d82 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -2532,6 +2532,10 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) if (human) info[jss::hostid] = getHostId(admin); + // domain: if configured with a domain, report it: + if (!app_.config().SERVER_DOMAIN.empty()) + info[jss::server_domain] = app_.config().SERVER_DOMAIN; + if (auto const netid = app_.overlay().networkID()) info[jss::network_id] = static_cast(*netid); diff --git a/src/ripple/app/misc/impl/Manifest.cpp b/src/ripple/app/misc/impl/Manifest.cpp index 52d488ef79..319d09b2c1 100644 --- a/src/ripple/app/misc/impl/Manifest.cpp +++ b/src/ripple/app/misc/impl/Manifest.cpp @@ -90,30 +90,9 @@ deserializeManifest(Slice s) { auto const d = st.getFieldVL(sfDomain); - // The domain must be between 4 and 128 characters long - if (boost::algorithm::clamp(d.size(), 4, 128) != d.size()) - return boost::none; - m.domain.assign(reinterpret_cast(d.data()), d.size()); - // This regular expression should do a decent job of weeding out - // obviously wrong domain names but it isn't perfect. It does not - // really support IDNs. If this turns out to be an issue, a more - // thorough regex can be used or this check can just be removed. - static boost::regex const re( - "^" // Beginning of line - "(" // Beginning of a segment - "(?!-)" // - must not begin with '-' - "[a-zA-Z0-9-]{1,63}" // - only alphanumeric and '-' - "(? to_uint64(std::string const& s); +/** Determines if the given string looks like a TOML-file hosting domain. + + Do not use this function to determine if a particular string is a valid + domain, as this function may reject domains that are otherwise valid and + doesn't check whether the TLD is valid. + */ +bool +isProperlyFormedTomlDomain(std::string const& domain); + } // namespace ripple #endif diff --git a/src/ripple/basics/impl/StringUtilities.cpp b/src/ripple/basics/impl/StringUtilities.cpp index 0481651d96..d76bd79b6b 100644 --- a/src/ripple/basics/impl/StringUtilities.cpp +++ b/src/ripple/basics/impl/StringUtilities.cpp @@ -120,4 +120,31 @@ to_uint64(std::string const& s) return boost::none; } +bool +isProperlyFormedTomlDomain(std::string const& domain) +{ + // The domain must be between 4 and 128 characters long + if (domain.size() < 4 || domain.size() > 128) + return false; + + // This regular expression should do a decent job of weeding out + // obviously wrong domain names but it isn't perfect. It does not + // really support IDNs. If this turns out to be an issue, a more + // thorough regex can be used or this check can just be removed. + static boost::regex const re( + "^" // Beginning of line + "(" // Beginning of a segment + "(?!-)" // - must not begin with '-' + "[a-zA-Z0-9-]{1,63}" // - only alphanumeric and '-' + "(?> features; + std::string SERVER_DOMAIN; + public: Config() : j_{beast::Journal::getNullSink()} { diff --git a/src/ripple/core/ConfigSections.h b/src/ripple/core/ConfigSections.h index c7467d032f..e3b864d858 100644 --- a/src/ripple/core/ConfigSections.h +++ b/src/ripple/core/ConfigSections.h @@ -81,6 +81,7 @@ struct ConfigSection #define SECTION_SSL_VERIFY "ssl_verify" #define SECTION_SSL_VERIFY_FILE "ssl_verify_file" #define SECTION_SSL_VERIFY_DIR "ssl_verify_dir" +#define SECTION_SERVER_DOMAIN "server_domain" #define SECTION_VALIDATORS_FILE "validators_file" #define SECTION_VALIDATION_SEED "validation_seed" #define SECTION_WEBSOCKET_PING_FREQ "websocket_ping_frequency" diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 63c6a8ed3a..d8c88bec69 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -496,6 +497,18 @@ Config::loadFromString(std::string const& fileContents) MAX_JOB_QUEUE_TX); } + if (getSingleSection(secConfig, SECTION_SERVER_DOMAIN, strTemp, j_)) + { + if (!isProperlyFormedTomlDomain(strTemp)) + { + Throw( + "Invalid " SECTION_SERVER_DOMAIN + ": the domain name does not appear to meet the requirements."); + } + + SERVER_DOMAIN = strTemp; + } + if (getSingleSection( secConfig, SECTION_AMENDMENT_MAJORITY_TIME, strTemp, j_)) { diff --git a/src/ripple/overlay/README.md b/src/ripple/overlay/README.md index ca22f1e8ae..5f6a287d6c 100644 --- a/src/ripple/overlay/README.md +++ b/src/ripple/overlay/README.md @@ -243,6 +243,24 @@ encoded in base58 using the standard encoding for node public keys. See: https://xrpl.org/base58-encodings.html + +| Field Name | Request | Response | +|--------------------- |:-----------------: |:-----------------: | +| `Server-Domain` | :white_check_mark: | :white_check_mark: | + +The optional `Server-Domain` field allows a server to report the domain that +it is operating under. The value is configured by the server administrator in +the configuration file using the `[server_domain]` key. + +The value is advisory and is not used by the code at this time, except for +reporting purposes. External tools should verify this value prior to using +it by attempting to locate a [TOML file](https://xrpl.org/xrp-ledger-toml.html) +under the specified domain and locating the public key of this server under the +`[NODES]` key. + +Sending a malformed domain will prevent a connection from being established. + + | Field Name | Request | Response | |--------------------- |:-----------------: |:-----------------: | | `Session-Signature` | :heavy_check_mark: | :heavy_check_mark: | @@ -254,7 +272,8 @@ The value is presently encoded using **Base64** encoding, but implementations should support both **Base64** and **HEX** encoding for this value. For more details on this field, please see **Session Signature** below. - + + | Field Name | Request | Response | |--------------------- |:-----------------: |:-----------------: | | `Crawl` | :white_check_mark: | :white_check_mark: | diff --git a/src/ripple/overlay/impl/Handshake.cpp b/src/ripple/overlay/impl/Handshake.cpp index 190a12122f..d2f6f966dc 100644 --- a/src/ripple/overlay/impl/Handshake.cpp +++ b/src/ripple/overlay/impl/Handshake.cpp @@ -129,6 +129,9 @@ buildHandshake( h.insert("Session-Signature", base64_encode(sig.data(), sig.size())); } + if (!app.config().SERVER_DOMAIN.empty()) + h.insert("Server-Domain", app.config().SERVER_DOMAIN); + if (beast::IP::is_public(remote_ip)) h.insert("Remote-IP", remote_ip.to_string()); @@ -157,18 +160,21 @@ verifyHandshake( beast::IP::Address remote, Application& app) { - if (networkID) + if (auto const iter = headers.find("Server-Domain"); iter != headers.end()) { - if (auto const iter = headers.find("Network-ID"); iter != headers.end()) - { - std::uint32_t nid; + if (!isProperlyFormedTomlDomain(iter->value().to_string())) + throw std::runtime_error("Invalid server domain"); + } - if (!beast::lexicalCastChecked(nid, iter->value().to_string())) - throw std::runtime_error("Invalid peer network identifier"); + if (auto const iter = headers.find("Network-ID"); iter != headers.end()) + { + std::uint32_t nid; - if (nid != *networkID) - throw std::runtime_error("Peer is on a different network"); - } + if (!beast::lexicalCastChecked(nid, iter->value().to_string())) + throw std::runtime_error("Invalid peer network identifier"); + + if (networkID && nid != *networkID) + throw std::runtime_error("Peer is on a different network"); } if (auto const iter = headers.find("Network-Time"); iter != headers.end()) diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index c7d2a3aadf..fa514253e0 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -108,7 +108,7 @@ PeerImp::~PeerImp() if (inCluster) { - JLOG(journal_.warn()) << getName() << " left cluster"; + JLOG(journal_.warn()) << name() << " left cluster"; } } @@ -249,10 +249,9 @@ PeerImp::send(std::shared_ptr const& m) journal_.active(beast::severities::kDebug) && (sendq_size % Tuning::sendQueueLogFreq) == 0) { - std::string const name{getName()}; - JLOG(journal_.debug()) - << (name.empty() ? remote_address_.to_string() : name) - << " sendq: " << sendq_size; + std::string const n = name(); + JLOG(journal_.debug()) << (n.empty() ? remote_address_.to_string() : n) + << " sendq: " << sendq_size; } send_queue_.push(m); @@ -325,19 +324,21 @@ PeerImp::json() { ret[jss::cluster] = true; - std::string name{getName()}; - if (!name.empty()) + if (auto const n = name(); !n.empty()) // Could move here if Json::Value supported moving from a string - ret[jss::name] = name; + ret[jss::name] = n; } + if (auto const d = domain(); !d.empty()) + ret[jss::server_domain] = domain(); + + if (auto const nid = headers_["Network-ID"].to_string(); !nid.empty()) + ret[jss::network_id] = nid; + ret[jss::load] = usage_.balance(); - { - auto const version = getVersion(); - if (!version.empty()) - ret[jss::version] = version; - } + if (auto const version = getVersion(); !version.empty()) + ret[jss::version] = version; ret[jss::protocol] = to_string(protocol_); @@ -537,10 +538,9 @@ PeerImp::fail(std::string const& reason) reason)); if (journal_.active(beast::severities::kWarning) && socket_.is_open()) { - std::string const name{getName()}; - JLOG(journal_.warn()) - << (name.empty() ? remote_address_.to_string() : name) - << " failed: " << reason; + std::string const n = name(); + JLOG(journal_.warn()) << (n.empty() ? remote_address_.to_string() : n) + << " failed: " << reason; } close(); } @@ -826,12 +826,18 @@ PeerImp::onWriteResponse(error_code ec, std::size_t bytes_transferred) } std::string -PeerImp::getName() const +PeerImp::name() const { std::shared_lock read_lock{nameMutex_}; return name_; } +std::string +PeerImp::domain() const +{ + return headers_["Server-Domain"].to_string(); +} + //------------------------------------------------------------------------------ // Protocol logic @@ -1125,7 +1131,7 @@ PeerImp::onMessage(std::shared_ptr const& m) if (item.address != beast::IP::Endpoint()) gossip.items.push_back(item); } - overlay_.resourceManager().importConsumers(getName(), gossip); + overlay_.resourceManager().importConsumers(name(), gossip); } // Calculate the cluster fee: diff --git a/src/ripple/overlay/impl/PeerImp.h b/src/ripple/overlay/impl/PeerImp.h index 489ac21ec2..dad3658420 100644 --- a/src/ripple/overlay/impl/PeerImp.h +++ b/src/ripple/overlay/impl/PeerImp.h @@ -477,9 +477,14 @@ private: void onWriteResponse(error_code ec, std::size_t bytes_transferred); - // A thread-safe way of getting the name. std::string - getName() const; + name() const; + + std::string + domain() const; + + std::optional + networkID() const; // // protocol message loop diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 468f3e00b6..216fdfe0f3 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -474,6 +474,7 @@ JSS(seq); // in: LedgerEntry; // ValidatorList, ValidatorInfo, Manifest JSS(seqNum); // out: LedgerToJson JSS(sequence_count); // out: AccountInfo +JSS(server_domain); // out: NetworkOPs JSS(server_state); // out: NetworkOPs JSS(server_state_duration_us); // out: NetworkOPs JSS(server_status); // out: NetworkOPs