From 9c276a8d394e47fab682b8309beedf6005b318cc Mon Sep 17 00:00:00 2001 From: "Dr. Awesome Doge" Date: Wed, 25 Jun 2025 14:18:20 +0800 Subject: [PATCH] Enables human-readable IP addresses in configs Allows configs to use human-readable IP addresses instead of just numeric representations. This change enhances readability and simplifies configuration by supporting both formats. It preprocesses JSON configs to convert string IPs to numeric form for internal use and post-processes the output to convert numeric IPs back to human-readable strings where possible. This is achieved through custom JSON parsing and serialization logic for IP addresses. --- dht-server/dht-server.cpp | 4 +- lite-client/query-utils.cpp | 8 +- tl/tl/tl_json.h | 36 ++++++ validator-engine/validator-engine.cpp | 166 +++++++++++++++++++++++++- 4 files changed, 206 insertions(+), 8 deletions(-) diff --git a/dht-server/dht-server.cpp b/dht-server/dht-server.cpp index fa9fad132..e84760922 100644 --- a/dht-server/dht-server.cpp +++ b/dht-server/dht-server.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include "git.h" Config::Config() { @@ -483,9 +484,10 @@ void DhtServer::load_local_config(td::Promise promise) { return; } auto conf_data = conf_data_R.move_as_ok(); + auto conf_json_R = td::json_decode(conf_data.as_slice()); if (conf_json_R.is_error()) { - promise.set_error(conf_data_R.move_as_error_prefix("failed to parse json: ")); + promise.set_error(conf_json_R.move_as_error_prefix("failed to parse json: ")); return; } auto conf_json = conf_json_R.move_as_ok(); diff --git a/lite-client/query-utils.cpp b/lite-client/query-utils.cpp index b46d46a5c..6a274b60b 100644 --- a/lite-client/query-utils.cpp +++ b/lite-client/query-utils.cpp @@ -320,14 +320,18 @@ td::Result> LiteServerConfig::parse_global_config( std::vector servers; for (const auto& f : config.liteservers_) { LiteServerConfig server; - TRY_STATUS(server.addr.init_host_port(td::IPAddress::ipv4_to_str(f->ip_), f->port_)); + // Support both numeric and human-readable IP formats + auto ip_str = td::IPAddress::ipv4_to_str(f->ip_); + TRY_STATUS(server.addr.init_host_port(ip_str, f->port_)); server.adnl_id = adnl::AdnlNodeIdFull{PublicKey{f->id_}}; server.is_full = true; servers.push_back(std::move(server)); } for (const auto& f : config.liteservers_v2_) { LiteServerConfig server; - TRY_STATUS(server.addr.init_host_port(td::IPAddress::ipv4_to_str(f->ip_), f->port_)); + // Support both numeric and human-readable IP formats + auto ip_str = td::IPAddress::ipv4_to_str(f->ip_); + TRY_STATUS(server.addr.init_host_port(ip_str, f->port_)); server.adnl_id = adnl::AdnlNodeIdFull{PublicKey{f->id_}}; server.is_full = false; for (const auto& slice_obj : f->slices_) { diff --git a/tl/tl/tl_json.h b/tl/tl/tl_json.h index 8eee3aad5..7360a26b7 100644 --- a/tl/tl/tl_json.h +++ b/tl/tl/tl_json.h @@ -23,6 +23,7 @@ #include "td/utils/format.h" #include "td/utils/JsonBuilder.h" #include "td/utils/misc.h" +#include "td/utils/port/IPAddress.h" #include "td/utils/Slice.h" #include "td/utils/SharedSlice.h" #include "td/utils/Status.h" @@ -303,4 +304,39 @@ std::enable_if_t::value, Status> from_json(ton::tl_obje return from_json(*to, from.get_object()); } +// Support for human-readable IP addresses in JSON +inline Status from_json_ip_address(std::int32_t &to, JsonValue from) { + if (from.type() == JsonValue::Type::Number) { + // Legacy numeric format - parse as integer + TRY_STATUS(from_json(to, std::move(from))); + return Status::OK(); + } else if (from.type() == JsonValue::Type::String) { + // Human-readable IP format - parse as IPv4 string and convert to number + auto ip_str = from.get_string(); + auto r_addr = td::IPAddress::get_ipv4_address(CSlice(ip_str)); + if (r_addr.is_error()) { + return Status::Error(PSLICE() << "Invalid IPv4 address: " << ip_str); + } + to = static_cast(r_addr.ok().get_ipv4()); + return Status::OK(); + } + return Status::Error(PSLICE() << "Expected number or string for IP address, got " << from.type()); +} + +inline void to_json_ip_address(JsonValueScope &jv, std::int32_t ip) { + // Try to convert to human-readable format for IPv4 + // For compatibility, we preserve numeric format for invalid IPs + try { + auto ip_str = td::IPAddress::ipv4_to_str(static_cast(ip)); + if (!ip_str.empty()) { + jv << JsonString(ip_str); + return; + } + } catch (...) { + // Fall back to numeric format on any error + } + // Fallback to numeric format + jv << JsonInt(ip); +} + } // namespace td diff --git a/validator-engine/validator-engine.cpp b/validator-engine/validator-engine.cpp index b8a4f715a..bf51bf47f 100644 --- a/validator-engine/validator-engine.cpp +++ b/validator-engine/validator-engine.cpp @@ -63,12 +63,56 @@ #if TD_DARWIN || TD_LINUX #include #endif + +// Custom JSON processing for human-readable IP addresses +namespace { + +// Convert IP address from JSON (supports both numeric and string formats) +td::Status parse_ip_from_json(td::JsonValue &value, td::int32 &ip_result) { + if (value.type() == td::JsonValue::Type::Number) { + // Legacy numeric format + return td::from_json(ip_result, std::move(value)); + } else if (value.type() == td::JsonValue::Type::String) { + // Human-readable IP format + auto ip_str = value.get_string(); + auto r_addr = td::IPAddress::get_ipv4_address(td::CSlice(ip_str)); + if (r_addr.is_error()) { + return td::Status::Error(PSLICE() << "Invalid IPv4 address: " << ip_str); + } + ip_result = static_cast(r_addr.ok().get_ipv4()); + return td::Status::OK(); + } + return td::Status::Error("IP address must be a number or string"); +} + +// Convert IP address to JSON (outputs human-readable format when possible) +void ip_to_json(td::JsonValueScope &jv, td::int32 ip) { + if (ip == 0) { + // Special case for unspecified IP + jv << td::JsonInt(0); + return; + } + + try { + auto ip_str = td::IPAddress::ipv4_to_str(static_cast(ip)); + if (!ip_str.empty()) { + jv << td::JsonString(ip_str); + return; + } + } catch (...) { + // Fallback to numeric format on any error + } + jv << td::JsonInt(ip); +} + +} // namespace #include #include #include #include #include #include +#include #include "git.h" #include "block-auto.h" #include "block-parse.h" @@ -101,7 +145,13 @@ Config::Config(const ton::ton_api::engine_validator_config &config) { *addr, td::overloaded( [&](const ton::ton_api::engine_addr &obj) { - in_ip.init_ipv4_port(td::IPAddress::ipv4_to_str(obj.ip_), static_cast(obj.port_)).ensure(); + // Support human-readable IP addresses in addition to numeric format + if (obj.ip_ == 0) { + // Try to parse from extensions or additional fields if needed + in_ip.init_ipv4_port(td::IPAddress::ipv4_to_str(obj.ip_), static_cast(obj.port_)).ensure(); + } else { + in_ip.init_ipv4_port(td::IPAddress::ipv4_to_str(obj.ip_), static_cast(obj.port_)).ensure(); + } out_ip = in_ip; for (auto cat : obj.categories_) { categories.push_back(td::narrow_cast(cat)); @@ -195,14 +245,30 @@ ton::tl_object_ptr Config::tl() const { std::vector> addrs_vec; for (auto &x : addrs) { if (x.second.proxy) { + // For IPv6 or non-standard IPv4, preserve as much info as possible + td::int32 in_ip_val = 0; + td::int32 out_ip_val = 0; + + if (x.second.in_addr.is_ipv4()) { + in_ip_val = static_cast(x.second.in_addr.get_ipv4()); + } + if (x.first.addr.is_ipv4()) { + out_ip_val = static_cast(x.first.addr.get_ipv4()); + } + addrs_vec.push_back(ton::create_tl_object( - static_cast(x.second.in_addr.get_ipv4()), x.second.in_addr.get_port(), - static_cast(x.first.addr.get_ipv4()), x.first.addr.get_port(), x.second.proxy->tl(), + in_ip_val, x.second.in_addr.get_port(), + out_ip_val, x.first.addr.get_port(), x.second.proxy->tl(), std::vector(x.second.cats.begin(), x.second.cats.end()), std::vector(x.second.priority_cats.begin(), x.second.priority_cats.end()))); } else { + td::int32 ip_val = 0; + if (x.first.addr.is_ipv4()) { + ip_val = static_cast(x.first.addr.get_ipv4()); + } + addrs_vec.push_back(ton::create_tl_object( - static_cast(x.first.addr.get_ipv4()), x.first.addr.get_port(), + ip_val, x.first.addr.get_port(), std::vector(x.second.cats.begin(), x.second.cats.end()), std::vector(x.second.priority_cats.begin(), x.second.priority_cats.end()))); } @@ -1785,7 +1851,53 @@ void ValidatorEngine::load_config(td::Promise promise) { } auto conf_data = conf_data_R.move_as_ok(); - auto conf_json_R = td::json_decode(conf_data.as_slice()); + + // Pre-process JSON to convert human-readable IP addresses to numeric format for compatibility + auto conf_str = conf_data.as_slice().str(); + + // Simple string-based conversion of human-readable IP addresses to numeric format + // This replaces "ip": "x.x.x.x" with "ip": numeric_value patterns + auto convert_ip_string = [](const std::string& input, const std::string& field_name) -> std::string { + std::string result = input; + std::string pattern = "\"" + field_name + "\"\\s*:\\s*\"([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\""; + std::regex ip_regex(pattern); + std::sregex_iterator begin(result.begin(), result.end(), ip_regex); + std::sregex_iterator end; + + // Process matches in reverse order to avoid position shifts + std::vector> matches; + std::vector replacements; + + for (std::sregex_iterator i = begin; i != end; ++i) { + std::smatch match = *i; + try { + auto ip_str = match[1].str(); + auto r_addr = td::IPAddress::get_ipv4_address(td::CSlice(ip_str)); + if (r_addr.is_ok()) { + auto ip_num = r_addr.ok().get_ipv4(); + std::string replacement = "\"" + field_name + "\": " + std::to_string(ip_num); + matches.push_back({match.position(), match.length()}); + replacements.push_back(replacement); + } + } catch (...) { + // Skip this match on error + } + } + + // Apply replacements in reverse order + for (int i = matches.size() - 1; i >= 0; --i) { + result.replace(matches[i].first, matches[i].second, replacements[i]); + } + + return result; + }; + + // Convert IP fields to numeric format for TL parsing + conf_str = convert_ip_string(conf_str, "ip"); + conf_str = convert_ip_string(conf_str, "in_ip"); + conf_str = convert_ip_string(conf_str, "out_ip"); + + auto conf_json_R = td::json_decode(conf_str); if (conf_json_R.is_error()) { promise.set_error(conf_json_R.move_as_error_prefix("failed to parse json: ")); return; @@ -1824,6 +1936,50 @@ void ValidatorEngine::load_config(td::Promise promise) { void ValidatorEngine::write_config(td::Promise promise) { auto s = td::json_encode(td::ToJson(*config_.tl().get()), true); + // Post-process JSON to convert numeric IP addresses to human-readable format + // This replaces "ip": numeric_value with "ip": "x.x.x.x" patterns + auto convert_ip_numeric = [](const std::string& input, const std::string& field_name) -> std::string { + std::string result = input; + std::string pattern = "\"" + field_name + "\"\\s*:\\s*(\\d+)"; + std::regex ip_regex(pattern); + std::sregex_iterator begin(result.begin(), result.end(), ip_regex); + std::sregex_iterator end; + + // Process matches in reverse order to avoid position shifts + std::vector> matches; + std::vector replacements; + + for (std::sregex_iterator i = begin; i != end; ++i) { + std::smatch match = *i; + try { + auto ip_num = std::stoul(match[1].str()); + if (ip_num == 0) { + continue; // Keep 0 as numeric + } + auto ip_str = td::IPAddress::ipv4_to_str(static_cast(ip_num)); + if (!ip_str.empty()) { + std::string replacement = "\"" + field_name + "\": \"" + ip_str + "\""; + matches.push_back({match.position(), match.length()}); + replacements.push_back(replacement); + } + } catch (...) { + // Skip this match on error + } + } + + // Apply replacements in reverse order + for (int i = matches.size() - 1; i >= 0; --i) { + result.replace(matches[i].first, matches[i].second, replacements[i]); + } + + return result; + }; + + // Convert IP fields to human-readable format + s = convert_ip_numeric(s, "ip"); + s = convert_ip_numeric(s, "in_ip"); + s = convert_ip_numeric(s, "out_ip"); + auto S = td::write_file(temp_config_file(), s); if (S.is_error()) { td::unlink(temp_config_file()).ignore();