1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/net/GetHostByNameActor.h"
8 
9 #include "td/net/HttpQuery.h"
10 #include "td/net/SslStream.h"
11 #include "td/net/Wget.h"
12 
13 #include "td/utils/common.h"
14 #include "td/utils/JsonBuilder.h"
15 #include "td/utils/logging.h"
16 #include "td/utils/misc.h"
17 #include "td/utils/Slice.h"
18 #include "td/utils/SliceBuilder.h"
19 #include "td/utils/Time.h"
20 
21 namespace td {
22 namespace detail {
23 
24 class GoogleDnsResolver final : public Actor {
25  public:
GoogleDnsResolver(std::string host,bool prefer_ipv6,Promise<IPAddress> promise)26   GoogleDnsResolver(std::string host, bool prefer_ipv6, Promise<IPAddress> promise)
27       : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) {
28   }
29 
30  private:
31   std::string host_;
32   bool prefer_ipv6_;
33   Promise<IPAddress> promise_;
34   ActorOwn<Wget> wget_;
35   double begin_time_ = 0;
36 
start_up()37   void start_up() final {
38     auto r_address = IPAddress::get_ip_address(host_);
39     if (r_address.is_ok()) {
40       promise_.set_value(r_address.move_as_ok());
41       return stop();
42     }
43 
44     const int timeout = 10;
45     const int ttl = 3;
46     begin_time_ = Time::now();
47     auto wget_promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result<unique_ptr<HttpQuery>> r_http_query) {
48       send_closure(actor_id, &GoogleDnsResolver::on_result, std::move(r_http_query));
49     });
50     wget_ = create_actor<Wget>(
51         "GoogleDnsResolver", std::move(wget_promise),
52         PSTRING() << "https://dns.google/resolve?name=" << url_encode(host_) << "&type=" << (prefer_ipv6_ ? 28 : 1),
53         std::vector<std::pair<string, string>>({{"Host", "dns.google"}}), timeout, ttl, prefer_ipv6_,
54         SslStream::VerifyPeer::Off);
55   }
56 
get_ip_address(Result<unique_ptr<HttpQuery>> r_http_query)57   static Result<IPAddress> get_ip_address(Result<unique_ptr<HttpQuery>> r_http_query) {
58     TRY_RESULT(http_query, std::move(r_http_query));
59     TRY_RESULT(json_value, json_decode(http_query->content_));
60     if (json_value.type() != JsonValue::Type::Object) {
61       return Status::Error("Failed to parse DNS result: not an object");
62     }
63     TRY_RESULT(answer, get_json_object_field(json_value.get_object(), "Answer", JsonValue::Type::Array, false));
64     auto &array = answer.get_array();
65     if (array.empty()) {
66       return Status::Error("Failed to parse DNS result: Answer is an empty array");
67     }
68     if (array[0].type() != JsonValue::Type::Object) {
69       return Status::Error("Failed to parse DNS result: Answer[0] is not an object");
70     }
71     auto &answer_0 = array[0].get_object();
72     TRY_RESULT(ip_str, get_json_object_string_field(answer_0, "data", false));
73     IPAddress ip;
74     TRY_STATUS(ip.init_host_port(ip_str, 0));
75     return ip;
76   }
77 
on_result(Result<unique_ptr<HttpQuery>> r_http_query)78   void on_result(Result<unique_ptr<HttpQuery>> r_http_query) {
79     auto end_time = Time::now();
80     auto result = get_ip_address(std::move(r_http_query));
81     VLOG(dns_resolver) << "Init IPv" << (prefer_ipv6_ ? "6" : "4") << " host = " << host_ << " in "
82                        << end_time - begin_time_ << " seconds to "
83                        << (result.is_ok() ? (PSLICE() << result.ok()) : CSlice("[invalid]"));
84     promise_.set_result(std::move(result));
85     stop();
86   }
87 };
88 
89 class NativeDnsResolver final : public Actor {
90  public:
NativeDnsResolver(std::string host,bool prefer_ipv6,Promise<IPAddress> promise)91   NativeDnsResolver(std::string host, bool prefer_ipv6, Promise<IPAddress> promise)
92       : host_(std::move(host)), prefer_ipv6_(prefer_ipv6), promise_(std::move(promise)) {
93   }
94 
95  private:
96   std::string host_;
97   bool prefer_ipv6_;
98   Promise<IPAddress> promise_;
99 
start_up()100   void start_up() final {
101     IPAddress ip;
102     auto begin_time = Time::now();
103     auto status = ip.init_host_port(host_, 0, prefer_ipv6_);
104     auto end_time = Time::now();
105     VLOG(dns_resolver) << "Init host = " << host_ << " in " << end_time - begin_time << " seconds to " << ip;
106     if (status.is_error()) {
107       promise_.set_error(std::move(status));
108     } else {
109       promise_.set_value(std::move(ip));
110     }
111     stop();
112   }
113 };
114 
115 }  // namespace detail
116 
117 int VERBOSITY_NAME(dns_resolver) = VERBOSITY_NAME(DEBUG);
118 
GetHostByNameActor(Options options)119 GetHostByNameActor::GetHostByNameActor(Options options) : options_(std::move(options)) {
120   CHECK(!options_.resolver_types.empty());
121 }
122 
run(string host,int port,bool prefer_ipv6,Promise<IPAddress> promise)123 void GetHostByNameActor::run(string host, int port, bool prefer_ipv6, Promise<IPAddress> promise) {
124   if (host.empty()) {
125     return promise.set_error(Status::Error("Host is empty"));
126   }
127 
128   auto r_ascii_host = idn_to_ascii(host);
129   if (r_ascii_host.is_error()) {
130     return promise.set_error(r_ascii_host.move_as_error());
131   }
132   auto ascii_host = r_ascii_host.move_as_ok();
133 
134   auto begin_time = Time::now();
135   auto &value = cache_[prefer_ipv6].emplace(ascii_host, Value{{}, begin_time - 1.0}).first->second;
136   if (value.expires_at > begin_time) {
137     return promise.set_result(value.get_ip_port(port));
138   }
139 
140   auto &query = active_queries_[prefer_ipv6][ascii_host];
141   query.promises.emplace_back(port, std::move(promise));
142   if (query.query.empty()) {
143     CHECK(query.promises.size() == 1);
144     query.real_host = std::move(host);
145     query.begin_time = Time::now();
146     run_query(std::move(ascii_host), prefer_ipv6, query);
147   }
148 }
149 
run_query(std::string host,bool prefer_ipv6,Query & query)150 void GetHostByNameActor::run_query(std::string host, bool prefer_ipv6, Query &query) {
151   auto promise = PromiseCreator::lambda([actor_id = actor_id(this), host, prefer_ipv6](Result<IPAddress> res) mutable {
152     send_closure(actor_id, &GetHostByNameActor::on_query_result, std::move(host), prefer_ipv6, std::move(res));
153   });
154 
155   CHECK(query.query.empty());
156   CHECK(query.pos < options_.resolver_types.size());
157   auto resolver_type = options_.resolver_types[query.pos++];
158   query.query = [&] {
159     switch (resolver_type) {
160       case ResolverType::Native:
161         return ActorOwn<>(create_actor_on_scheduler<detail::NativeDnsResolver>(
162             "NativeDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise)));
163       case ResolverType::Google:
164         return ActorOwn<>(create_actor_on_scheduler<detail::GoogleDnsResolver>(
165             "GoogleDnsResolver", options_.scheduler_id, std::move(host), prefer_ipv6, std::move(promise)));
166       default:
167         UNREACHABLE();
168         return ActorOwn<>();
169     }
170   }();
171 }
172 
on_query_result(std::string host,bool prefer_ipv6,Result<IPAddress> result)173 void GetHostByNameActor::on_query_result(std::string host, bool prefer_ipv6, Result<IPAddress> result) {
174   auto query_it = active_queries_[prefer_ipv6].find(host);
175   CHECK(query_it != active_queries_[prefer_ipv6].end());
176   auto &query = query_it->second;
177   CHECK(!query.promises.empty());
178   CHECK(!query.query.empty());
179 
180   if (result.is_error() && query.pos < options_.resolver_types.size()) {
181     query.query.reset();
182     return run_query(std::move(host), prefer_ipv6, query);
183   }
184 
185   auto end_time = Time::now();
186   VLOG(dns_resolver) << "Init host = " << query.real_host << " in total of " << end_time - query.begin_time
187                      << " seconds to " << (result.is_ok() ? (PSLICE() << result.ok()) : CSlice("[invalid]"));
188 
189   auto promises = std::move(query.promises);
190   auto value_it = cache_[prefer_ipv6].find(host);
191   CHECK(value_it != cache_[prefer_ipv6].end());
192   auto cache_timeout = result.is_ok() ? options_.ok_timeout : options_.error_timeout;
193   value_it->second = Value{std::move(result), end_time + cache_timeout};
194   active_queries_[prefer_ipv6].erase(query_it);
195 
196   for (auto &promise : promises) {
197     promise.second.set_result(value_it->second.get_ip_port(promise.first));
198   }
199 }
200 
201 }  // namespace td
202