Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dht-server/dht-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include <sstream>
#include <cstdlib>
#include <set>
#include <regex>
#include "git.h"

Config::Config() {
Expand Down Expand Up @@ -483,9 +484,10 @@ void DhtServer::load_local_config(td::Promise<td::Unit> 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();
Expand Down
8 changes: 6 additions & 2 deletions lite-client/query-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,14 +320,18 @@ td::Result<std::vector<LiteServerConfig>> LiteServerConfig::parse_global_config(
std::vector<LiteServerConfig> 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_) {
Expand Down
36 changes: 36 additions & 0 deletions tl/tl/tl_json.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -303,4 +304,39 @@ std::enable_if_t<std::is_constructible<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<std::int32_t>(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<td::uint32>(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
166 changes: 161 additions & 5 deletions validator-engine/validator-engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,56 @@
#if TD_DARWIN || TD_LINUX
#include <unistd.h>
#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<td::int32>(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<td::uint32>(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 <algorithm>
#include <iostream>
#include <cstdlib>
#include <limits>
#include <set>
#include <cstdio>
#include <regex>
#include "git.h"
#include "block-auto.h"
#include "block-parse.h"
Expand Down Expand Up @@ -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<td::uint16>(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<td::uint16>(obj.port_)).ensure();
} else {
in_ip.init_ipv4_port(td::IPAddress::ipv4_to_str(obj.ip_), static_cast<td::uint16>(obj.port_)).ensure();
}
out_ip = in_ip;
for (auto cat : obj.categories_) {
categories.push_back(td::narrow_cast<td::uint8>(cat));
Expand Down Expand Up @@ -195,14 +245,30 @@ ton::tl_object_ptr<ton::ton_api::engine_validator_config> Config::tl() const {
std::vector<ton::tl_object_ptr<ton::ton_api::engine_Addr>> 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<td::int32>(x.second.in_addr.get_ipv4());
}
if (x.first.addr.is_ipv4()) {
out_ip_val = static_cast<td::int32>(x.first.addr.get_ipv4());
}

addrs_vec.push_back(ton::create_tl_object<ton::ton_api::engine_addrProxy>(
static_cast<td::int32>(x.second.in_addr.get_ipv4()), x.second.in_addr.get_port(),
static_cast<td::int32>(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<td::int32>(x.second.cats.begin(), x.second.cats.end()),
std::vector<td::int32>(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<td::int32>(x.first.addr.get_ipv4());
}

addrs_vec.push_back(ton::create_tl_object<ton::ton_api::engine_addr>(
static_cast<td::int32>(x.first.addr.get_ipv4()), x.first.addr.get_port(),
ip_val, x.first.addr.get_port(),
std::vector<td::int32>(x.second.cats.begin(), x.second.cats.end()),
std::vector<td::int32>(x.second.priority_cats.begin(), x.second.priority_cats.end())));
}
Expand Down Expand Up @@ -1785,7 +1851,53 @@ void ValidatorEngine::load_config(td::Promise<td::Unit> 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<std::pair<size_t, size_t>> matches;
std::vector<std::string> 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;
Expand Down Expand Up @@ -1824,6 +1936,50 @@ void ValidatorEngine::load_config(td::Promise<td::Unit> promise) {
void ValidatorEngine::write_config(td::Promise<td::Unit> promise) {
auto s = td::json_encode<std::string>(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<std::pair<size_t, size_t>> matches;
std::vector<std::string> 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<td::uint32>(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();
Expand Down