2018-12-31 20:04:05 +01:00
|
|
|
//
|
2018-12-31 23:02:34 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
|
2018-12-31 20:04:05 +01:00
|
|
|
//
|
|
|
|
// 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/utils/logging.h"
|
|
|
|
#include "td/utils/Time.h"
|
2019-01-24 10:47:54 +01:00
|
|
|
#include "td/utils/JsonBuilder.h"
|
|
|
|
#include "td/net/Wget.h"
|
2018-12-31 20:04:05 +01:00
|
|
|
|
|
|
|
namespace td {
|
2019-01-24 10:47:54 +01:00
|
|
|
namespace detail {
|
|
|
|
class GoogleDnsResolver : public Actor {
|
|
|
|
public:
|
2019-01-24 14:07:11 +01:00
|
|
|
GoogleDnsResolver(std::string host, GetHostByNameActor::ResolveOptions options, td::Promise<td::IPAddress> promise)
|
2019-01-24 13:18:23 +01:00
|
|
|
: host_(std::move(host)), options_(std::move(options)), promise_(std::move(promise)) {
|
2019-01-24 10:47:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2019-01-24 13:18:23 +01:00
|
|
|
std::string host_;
|
2019-01-24 14:07:11 +01:00
|
|
|
GetHostByNameActor::ResolveOptions options_;
|
2019-01-24 13:18:23 +01:00
|
|
|
Promise<IPAddress> promise_;
|
2019-01-24 10:47:54 +01:00
|
|
|
ActorOwn<Wget> wget_;
|
|
|
|
|
2019-01-24 13:18:23 +01:00
|
|
|
void start_up() override {
|
|
|
|
const int timeout = 10;
|
|
|
|
const int ttl = 3;
|
|
|
|
wget_ = create_actor<Wget>("Wget", create_result_handler(std::move(promise_)),
|
|
|
|
PSTRING() << "https://www.google.com/resolve?name=" << url_encode(host_)
|
|
|
|
<< "&type=" << (options_.prefer_ipv6 ? 28 : 1),
|
|
|
|
std::vector<std::pair<string, string>>({{"Host", "dns.google.com"}}), timeout, ttl,
|
|
|
|
options_.prefer_ipv6, SslStream::VerifyPeer::Off);
|
|
|
|
}
|
|
|
|
|
|
|
|
Promise<HttpQueryPtr> create_result_handler(Promise<IPAddress> promise) {
|
|
|
|
return PromiseCreator::lambda([promise = std::move(promise)](Result<HttpQueryPtr> r_http_query) mutable {
|
2019-01-24 10:47:54 +01:00
|
|
|
promise.set_result([&]() -> Result<IPAddress> {
|
|
|
|
TRY_RESULT(http_query, std::move(r_http_query));
|
|
|
|
LOG(ERROR) << *http_query;
|
|
|
|
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));
|
|
|
|
if (answer.get_array().size() == 0) {
|
|
|
|
return Status::Error("Failed to parse dns result: Answer is an empty array");
|
|
|
|
}
|
|
|
|
if (answer.get_array()[0].type() != JsonValue::Type::Object) {
|
|
|
|
return Status::Error("Failed to parse dns result: Answer[0] is not an object");
|
|
|
|
}
|
|
|
|
auto &answer_0 = answer.get_array()[0].get_object();
|
|
|
|
TRY_RESULT(ip_str, get_json_object_string_field(answer_0, "data", false));
|
|
|
|
IPAddress ip;
|
2019-01-24 13:18:23 +01:00
|
|
|
TRY_STATUS(ip.init_host_port(ip_str, 0));
|
2019-01-24 10:47:54 +01:00
|
|
|
return ip;
|
|
|
|
}());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
2019-01-24 13:18:23 +01:00
|
|
|
class NativeDnsResolver : public Actor {
|
|
|
|
public:
|
2019-01-24 14:07:11 +01:00
|
|
|
NativeDnsResolver(std::string host, GetHostByNameActor::ResolveOptions options, td::Promise<td::IPAddress> promise)
|
2019-01-24 13:18:23 +01:00
|
|
|
: host_(std::move(host)), options_(std::move(options)), promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string host_;
|
2019-01-24 14:07:11 +01:00
|
|
|
GetHostByNameActor::ResolveOptions options_;
|
2019-01-24 13:18:23 +01:00
|
|
|
Promise<IPAddress> promise_;
|
|
|
|
|
|
|
|
void start_up() override {
|
|
|
|
IPAddress ip;
|
|
|
|
auto begin_time = td::Time::now();
|
|
|
|
auto status = ip.init_host_port(host_, 0, options_.prefer_ipv6);
|
|
|
|
auto end_time = td::Time::now();
|
|
|
|
LOG(WARNING) << "Init host = " << host_ << " in " << end_time - begin_time << " seconds to " << ip;
|
|
|
|
if (status.is_error()) {
|
|
|
|
promise_.set_error(std::move(status));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
promise_.set_value(std::move(ip));
|
|
|
|
stop();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
class DnsResolver : public Actor {
|
|
|
|
public:
|
2019-01-24 14:07:11 +01:00
|
|
|
DnsResolver(std::string host, GetHostByNameActor::ResolveOptions options, td::Promise<td::IPAddress> promise)
|
2019-01-24 13:18:23 +01:00
|
|
|
: host_(std::move(host)), options_(std::move(options)), promise_(std::move(promise)) {
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string host_;
|
2019-01-24 14:07:11 +01:00
|
|
|
GetHostByNameActor::ResolveOptions options_;
|
2019-01-24 13:18:23 +01:00
|
|
|
Promise<IPAddress> promise_;
|
|
|
|
ActorOwn<> query_;
|
|
|
|
size_t pos_ = 0;
|
2019-01-24 14:07:11 +01:00
|
|
|
GetHostByNameActor::ResolveType types[2] = {GetHostByNameActor::Google, GetHostByNameActor::Native};
|
2019-01-24 13:18:23 +01:00
|
|
|
|
|
|
|
void loop() override {
|
|
|
|
if (!query_.empty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (pos_ == 2) {
|
|
|
|
promise_.set_error(Status::Error("Failed to resolve ip"));
|
|
|
|
return stop();
|
|
|
|
}
|
|
|
|
options_.type = types[pos_];
|
|
|
|
query_ = GetHostByNameActor::resolve(host_, options_,
|
|
|
|
PromiseCreator::lambda([actor_id = actor_id(this)](Result<IPAddress> res) {
|
|
|
|
send_closure(actor_id, &DnsResolver::on_result, std::move(res));
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
void on_result(Result<IPAddress> res) {
|
|
|
|
query_.reset();
|
|
|
|
if (res.is_ok() || pos_ == 2) {
|
|
|
|
promise_.set_result(std::move(res));
|
|
|
|
return stop();
|
|
|
|
}
|
|
|
|
loop();
|
|
|
|
}
|
|
|
|
};
|
2019-01-24 10:47:54 +01:00
|
|
|
} // namespace detail
|
|
|
|
|
2019-01-24 14:07:11 +01:00
|
|
|
GetHostByNameActor::Options::Options() = default;
|
|
|
|
ActorOwn<> GetHostByNameActor::resolve(std::string host, ResolveOptions options, Promise<IPAddress> promise) {
|
2019-01-24 13:18:23 +01:00
|
|
|
switch (options.type) {
|
2019-01-24 14:07:11 +01:00
|
|
|
case Native:
|
2019-01-24 13:18:23 +01:00
|
|
|
return ActorOwn<>(create_actor_on_scheduler<detail::NativeDnsResolver>(
|
|
|
|
"NativeDnsResolver", options.scheduler_id, std::move(host), options, std::move(promise)));
|
2019-01-24 14:07:11 +01:00
|
|
|
case Google:
|
2019-01-24 13:18:23 +01:00
|
|
|
return ActorOwn<>(create_actor_on_scheduler<detail::GoogleDnsResolver>(
|
|
|
|
"GoogleDnsResolver", options.scheduler_id, std::move(host), options, std::move(promise)));
|
2019-01-24 14:07:11 +01:00
|
|
|
case All:
|
2019-01-24 13:18:23 +01:00
|
|
|
return ActorOwn<>(create_actor_on_scheduler<detail::DnsResolver>("DnsResolver", options.scheduler_id,
|
|
|
|
std::move(host), options, std::move(promise)));
|
|
|
|
}
|
2019-01-24 10:47:54 +01:00
|
|
|
}
|
2018-05-18 18:44:46 +02:00
|
|
|
|
2019-01-24 14:07:11 +01:00
|
|
|
GetHostByNameActor::GetHostByNameActor(Options options) : options_(options) {
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
2019-01-24 13:18:23 +01:00
|
|
|
void GetHostByNameActor::on_result(std::string host, bool prefer_ipv6, Result<IPAddress> res) {
|
|
|
|
auto &value = cache_[prefer_ipv6].emplace(host, Value{{}, 0}).first->second;
|
|
|
|
|
|
|
|
auto promises = std::move(value.promises);
|
|
|
|
auto end_time = td::Time::now();
|
|
|
|
if (res.is_ok()) {
|
2019-01-24 14:07:11 +01:00
|
|
|
value = Value{res.move_as_ok(), end_time + options_.ok_timeout};
|
2019-01-24 13:18:23 +01:00
|
|
|
} else {
|
2019-01-24 14:07:11 +01:00
|
|
|
value = Value{res.move_as_error(), end_time + options_.error_timeout};
|
2019-01-24 13:18:23 +01:00
|
|
|
}
|
|
|
|
for (auto &promise : promises) {
|
|
|
|
promise.second.set_result(value.get_ip_port(promise.first));
|
|
|
|
}
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
2019-01-24 13:18:23 +01:00
|
|
|
void GetHostByNameActor::run(string host, int port, bool prefer_ipv6, Promise<IPAddress> promise) {
|
2018-07-01 03:12:20 +02:00
|
|
|
auto &value = cache_[prefer_ipv6].emplace(host, Value{{}, 0}).first->second;
|
2018-12-31 20:04:05 +01:00
|
|
|
auto begin_time = td::Time::now();
|
|
|
|
if (value.expire_at > begin_time) {
|
2019-01-24 13:18:23 +01:00
|
|
|
return promise.set_result(value.get_ip_port(port));
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
|
2019-01-24 13:18:23 +01:00
|
|
|
value.promises.emplace_back(port, std::move(promise));
|
|
|
|
if (value.query.empty()) {
|
2019-01-24 14:07:11 +01:00
|
|
|
ResolveOptions options;
|
|
|
|
options.type = options_.type;
|
|
|
|
options.scheduler_id = options_.scheduler_id;
|
2019-01-24 13:18:23 +01:00
|
|
|
options.prefer_ipv6 = prefer_ipv6;
|
|
|
|
value.query =
|
|
|
|
resolve(host, options,
|
|
|
|
PromiseCreator::lambda([actor_id = actor_id(this), host, prefer_ipv6](Result<IPAddress> res) mutable {
|
|
|
|
send_closure(actor_id, &GetHostByNameActor::on_result, std::move(host), prefer_ipv6, std::move(res));
|
|
|
|
}));
|
2018-12-31 20:04:05 +01:00
|
|
|
}
|
|
|
|
}
|
2018-05-18 18:44:46 +02:00
|
|
|
|
2018-12-31 20:04:05 +01:00
|
|
|
} // namespace td
|