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();