summaryrefslogtreecommitdiff
path: root/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp')
-rw-r--r--protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp220
1 files changed, 194 insertions, 26 deletions
diff --git a/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp b/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp
index b6cdcca0f0..8a720083c0 100644
--- a/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp
+++ b/protocols/Telegram/tdlib/td/tdnet/td/net/GetHostByNameActor.cpp
@@ -1,48 +1,216 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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 {
-GetHostByNameActor::GetHostByNameActor(int32 ok_timeout, int32 error_timeout)
- : ok_timeout_(ok_timeout), error_timeout_(error_timeout) {
+namespace detail {
+
+class GoogleDnsResolver final : public Actor {
+ public:
+ GoogleDnsResolver(std::string host, bool prefer_ipv6, Promise<IPAddress> promise)
+ : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) {
+ }
+
+ private:
+ std::string host_;
+ bool prefer_ipv6_;
+ Promise<IPAddress> promise_;
+ ActorOwn<Wget> 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<unique_ptr<HttpQuery>> r_http_query) {
+ send_closure(actor_id, &GoogleDnsResolver::on_result, std::move(r_http_query));
+ });
+ wget_ = create_actor<Wget>(
+ "GoogleDnsResolver", std::move(wget_promise),
+ PSTRING() << "https://dns.google/resolve?name=" << url_encode(host_) << "&type=" << (prefer_ipv6_ ? 28 : 1),
+ std::vector<std::pair<string, string>>({{"Host", "dns.google"}}), timeout, ttl, prefer_ipv6_,
+ SslCtx::VerifyPeer::Off);
+ }
+
+ static Result<IPAddress> get_ip_address(Result<unique_ptr<HttpQuery>> r_http_query) {
+ TRY_RESULT(http_query, std::move(r_http_query));
+
+ auto get_ip_address = [](JsonValue &answer) -> Result<IPAddress> {
+ 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, get_json_object_string_field(answer_0, "data", false));
+ 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");
+ }
+ TRY_RESULT(answer, get_json_object_field(json_value.get_object(), "Answer", JsonValue::Type::Array, false));
+ return get_ip_address(answer);
+ }
+ }
+
+ void on_result(Result<unique_ptr<HttpQuery>> 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<IPAddress> promise)
+ : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) {
+ }
+
+ private:
+ std::string host_;
+ bool prefer_ipv6_;
+ Promise<IPAddress> 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(std::string host, int port, td::Promise<td::IPAddress> promise) {
- auto r_ip = load_ip(std::move(host), port);
- promise.set_result(std::move(r_ip));
+void GetHostByNameActor::run(string host, int port, bool prefer_ipv6, Promise<IPAddress> 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<Query>();
+ }
+ 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);
+ }
}
-Result<td::IPAddress> GetHostByNameActor::load_ip(string host, int port) {
- auto &value = cache_.emplace(host, Value{{}, 0}).first->second;
- auto begin_time = td::Time::now();
- if (value.expire_at > begin_time) {
- auto ip = value.ip.clone();
- if (ip.is_ok()) {
- ip.ok_ref().set_port(port);
- CHECK(ip.ok().get_port() == port);
+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<IPAddress> 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<detail::NativeDnsResolver>(
+ "NativeDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise)));
+ case ResolverType::Google:
+ return ActorOwn<>(create_actor_on_scheduler<detail::GoogleDnsResolver>(
+ "GoogleDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise)));
+ default:
+ UNREACHABLE();
+ return ActorOwn<>();
}
- return ip;
+ }();
+}
+
+void GetHostByNameActor::on_query_result(std::string host, bool prefer_ipv6, Result<IPAddress> 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);
}
- td::IPAddress ip;
- auto status = ip.init_host_port(host, port);
- auto end_time = td::Time::now();
- LOG(WARNING) << "Init host = " << host << ", port = " << port << " in " << end_time - begin_time << " seconds to "
- << ip;
+ 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]"));
- if (status.is_ok()) {
- value = Value{ip, end_time + ok_timeout_};
- return ip;
- } else {
- value = Value{status.clone(), end_time + error_timeout_};
- return std::move(status);
+ 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