// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/net/GetHostByNameActor.h" #include "td/net/HttpQuery.h" #include "td/net/SslCtx.h" #include "td/net/Wget.h" #include "td/utils/common.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" namespace td { namespace detail { class GoogleDnsResolver final : public Actor { public: GoogleDnsResolver(std::string host, bool prefer_ipv6, Promise promise) : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) { } private: std::string host_; bool prefer_ipv6_; Promise promise_; ActorOwn wget_; double begin_time_ = 0; void start_up() final { auto r_address = IPAddress::get_ip_address(host_); if (r_address.is_ok()) { promise_.set_value(r_address.move_as_ok()); return stop(); } const int timeout = 10; const int ttl = 3; begin_time_ = Time::now(); auto wget_promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result> r_http_query) { send_closure(actor_id, &GoogleDnsResolver::on_result, std::move(r_http_query)); }); wget_ = create_actor( "GoogleDnsResolver", std::move(wget_promise), PSTRING() << "https://dns.google/resolve?name=" << url_encode(host_) << "&type=" << (prefer_ipv6_ ? 28 : 1), std::vector>({{"Host", "dns.google"}}), timeout, ttl, prefer_ipv6_, SslCtx::VerifyPeer::Off); } static Result get_ip_address(Result> r_http_query) { TRY_RESULT(http_query, std::move(r_http_query)); auto get_ip_address = [](JsonValue &answer) -> Result { auto &array = answer.get_array(); if (array.empty()) { return Status::Error("Failed to parse DNS result: Answer is an empty array"); } if (array[0].type() != JsonValue::Type::Object) { return Status::Error("Failed to parse DNS result: Answer[0] is not an object"); } auto &answer_0 = array[0].get_object(); TRY_RESULT(ip_str, answer_0.get_required_string_field("data")); IPAddress ip; TRY_STATUS(ip.init_host_port(ip_str, 0)); return ip; }; if (!http_query->get_arg("Answer").empty()) { TRY_RESULT(answer, json_decode(http_query->get_arg("Answer"))); if (answer.type() != JsonValue::Type::Array) { return Status::Error("Expected JSON array"); } return get_ip_address(answer); } else { TRY_RESULT(json_value, json_decode(http_query->content_)); if (json_value.type() != JsonValue::Type::Object) { return Status::Error("Failed to parse DNS result: not an object"); } auto &object = json_value.get_object(); TRY_RESULT(answer, object.extract_required_field("Answer", JsonValue::Type::Array)); return get_ip_address(answer); } } void on_result(Result> r_http_query) { auto end_time = Time::now(); auto result = get_ip_address(std::move(r_http_query)); VLOG(dns_resolver) << "Init IPv" << (prefer_ipv6_ ? "6" : "4") << " host = " << host_ << " in " << end_time - begin_time_ << " seconds to " << (result.is_ok() ? (PSLICE() << result.ok()) : CSlice("[invalid]")); promise_.set_result(std::move(result)); stop(); } }; class NativeDnsResolver final : public Actor { public: NativeDnsResolver(std::string host, bool prefer_ipv6, Promise promise) : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) { } private: std::string host_; bool prefer_ipv6_; Promise promise_; void start_up() final { IPAddress ip; auto begin_time = Time::now(); auto status = ip.init_host_port(host_, 0, prefer_ipv6_); auto end_time = Time::now(); VLOG(dns_resolver) << "Init host = " << host_ << " in " << end_time - begin_time << " seconds to " << ip; if (status.is_error()) { promise_.set_error(std::move(status)); } else { promise_.set_value(std::move(ip)); } stop(); } }; } // namespace detail int VERBOSITY_NAME(dns_resolver) = VERBOSITY_NAME(DEBUG); GetHostByNameActor::GetHostByNameActor(Options options) : options_(std::move(options)) { CHECK(!options_.resolver_types.empty()); } void GetHostByNameActor::run(string host, int port, bool prefer_ipv6, Promise promise) { auto r_ascii_host = idn_to_ascii(host); if (r_ascii_host.is_error()) { return promise.set_error(r_ascii_host.move_as_error()); } auto ascii_host = r_ascii_host.move_as_ok(); if (ascii_host.empty()) { return promise.set_error(Status::Error("Host is empty")); } auto begin_time = Time::now(); auto &value = cache_[prefer_ipv6].emplace(ascii_host, Value{{}, begin_time - 1.0}).first->second; if (value.expires_at > begin_time) { return promise.set_result(value.get_ip_port(port)); } auto &query_ptr = active_queries_[prefer_ipv6][ascii_host]; if (query_ptr == nullptr) { query_ptr = make_unique(); } auto &query = *query_ptr; query.promises.emplace_back(port, std::move(promise)); if (query.query.empty()) { CHECK(query.promises.size() == 1); query.real_host = std::move(host); query.begin_time = Time::now(); run_query(std::move(ascii_host), prefer_ipv6, query); } } void GetHostByNameActor::run_query(std::string host, bool prefer_ipv6, Query &query) { auto promise = PromiseCreator::lambda([actor_id = actor_id(this), host, prefer_ipv6](Result res) mutable { send_closure(actor_id, &GetHostByNameActor::on_query_result, std::move(host), prefer_ipv6, std::move(res)); }); CHECK(query.query.empty()); CHECK(query.pos < options_.resolver_types.size()); auto resolver_type = options_.resolver_types[query.pos++]; query.query = [&] { switch (resolver_type) { case ResolverType::Native: return ActorOwn<>(create_actor_on_scheduler( "NativeDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise))); case ResolverType::Google: return ActorOwn<>(create_actor_on_scheduler( "GoogleDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise))); default: UNREACHABLE(); return ActorOwn<>(); } }(); } void GetHostByNameActor::on_query_result(std::string host, bool prefer_ipv6, Result result) { auto query_it = active_queries_[prefer_ipv6].find(host); CHECK(query_it != active_queries_[prefer_ipv6].end()); auto &query = *query_it->second; CHECK(!query.promises.empty()); CHECK(!query.query.empty()); if (result.is_error() && query.pos < options_.resolver_types.size()) { query.query.reset(); return run_query(std::move(host), prefer_ipv6, query); } auto end_time = Time::now(); VLOG(dns_resolver) << "Init host = " << query.real_host << " in total of " << end_time - query.begin_time << " seconds to " << (result.is_ok() ? (PSLICE() << result.ok()) : CSlice("[invalid]")); auto promises = std::move(query.promises); auto value_it = cache_[prefer_ipv6].find(host); CHECK(value_it != cache_[prefer_ipv6].end()); auto cache_timeout = result.is_ok() ? options_.ok_timeout : options_.error_timeout; value_it->second = Value{std::move(result), end_time + cache_timeout}; active_queries_[prefer_ipv6].erase(query_it); for (auto &promise : promises) { promise.second.set_result(value_it->second.get_ip_port(promise.first)); } } } // namespace td