diff --git a/td/telegram/ConfigManager.cpp b/td/telegram/ConfigManager.cpp index 095aec6d..87a5260e 100644 --- a/td/telegram/ConfigManager.cpp +++ b/td/telegram/ConfigManager.cpp @@ -53,9 +53,85 @@ #include #include +#include + namespace td { int VERBOSITY_NAME(config_recoverer) = VERBOSITY_NAME(INFO); +Result HttpDate::to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second) { + int64 res = 0; + if (year < 1970 || year > 2037) { + return td::Status::Error("invalid year"); + } + if (month < 1 || month > 12) { + return td::Status::Error("invalid month"); + } + if (day < 1 || day > days_in_month(year, month)) { + return td::Status::Error("invalid day"); + } + if (hour < 0 || hour > 24) { // is hour == 24 possible? + return td::Status::Error("invalid hour"); + } + if (minute < 0 || minute > 60) { + return td::Status::Error("invalid minute"); + } + if (second < 0 || second > 60) { + return td::Status::Error("invalid second"); + } + for (int y = 1970; y < year; y++) { + res += (is_leap(y) + 365) * seconds_in_day(); + } + for (int m = 1; m < month; m++) { + res += days_in_month(year, m) * seconds_in_day(); + } + res += (day - 1) * seconds_in_day(); + res += hour * 60 * 60; + res += minute * 60; + res += second; + return res; +} + +Result HttpDate::parse_http_date(std::string slice) { + td::Parser p(slice); + p.read_till(','); // ignore week day + p.skip(','); + p.skip_whitespaces(); + TRY_RESULT(day, to_integer_safe(p.read_word())); + auto month_name = p.read_word(); + to_lower_inplace(month_name); + TRY_RESULT(year, to_integer_safe(p.read_word())); + p.skip_whitespaces(); + p.skip_nofail('0'); + TRY_RESULT(hour, to_integer_safe(p.read_till(':'))); + p.skip(':'); + p.skip_nofail('0'); + TRY_RESULT(minute, to_integer_safe(p.read_till(':'))); + p.skip(':'); + p.skip_nofail('0'); + TRY_RESULT(second, to_integer_safe(p.read_word())); + auto gmt = p.read_word(); + TRY_STATUS(std::move(p.status())); + if (gmt != "GMT") { + return Status::Error("timezone must be GMT"); + } + + Slice month_names[12] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; + + int month = 0; + + for (int m = 1; m <= 12; m++) { + if (month_names[m - 1] == month_name) { + month = m; + break; + } + } + + if (month == 0) { + return Status::Error("Unknown month name"); + } + + return HttpDate::to_unix_time(year, month, day, hour, minute, second); +} Result decode_config(Slice input) { static auto rsa = td::RSA::from_pem( @@ -117,8 +193,8 @@ Result decode_config(Slice input) { return std::move(config); } -static ActorOwn<> get_simple_config_impl(Promise promise, int32 scheduler_id, string url, string host, - bool prefer_ipv6) { +static ActorOwn<> get_simple_config_impl(Promise promise, int32 scheduler_id, string url, + string host, bool prefer_ipv6) { VLOG(config_recoverer) << "Request simple config from " << url; #if TD_EMSCRIPTEN // FIXME return ActorOwn<>(); @@ -128,9 +204,12 @@ static ActorOwn<> get_simple_config_impl(Promise promise, int32 sc return ActorOwn<>(create_actor_on_scheduler( "Wget", scheduler_id, PromiseCreator::lambda([promise = std::move(promise)](Result> r_query) mutable { - promise.set_result([&]() -> Result { + promise.set_result([&]() -> Result { TRY_RESULT(http_query, std::move(r_query)); - return decode_config(http_query->content_); + SimpleConfigResult res; + res.r_http_date = HttpDate::parse_http_date(http_query->get_arg("date").str()); + res.r_config = decode_config(http_query->content_); + return res; }()); }), std::move(url), std::vector>({{"Host", std::move(host)}}), timeout, ttl, prefer_ipv6, @@ -138,7 +217,7 @@ static ActorOwn<> get_simple_config_impl(Promise promise, int32 sc #endif } -ActorOwn<> get_simple_config_azure(Promise promise, const ConfigShared *shared_config, bool is_test, +ActorOwn<> get_simple_config_azure(Promise promise, const ConfigShared *shared_config, bool is_test, int32 scheduler_id) { string url = PSTRING() << "https://software-download.microsoft.com/" << (is_test ? "test" : "prod") << "v2/config.txt"; @@ -146,8 +225,8 @@ ActorOwn<> get_simple_config_azure(Promise promise, const ConfigSh return get_simple_config_impl(std::move(promise), scheduler_id, std::move(url), "tcdnb.azureedge.net", prefer_ipv6); } -ActorOwn<> get_simple_config_google_dns(Promise promise, const ConfigShared *shared_config, bool is_test, - int32 scheduler_id) { +ActorOwn<> get_simple_config_google_dns(Promise promise, const ConfigShared *shared_config, + bool is_test, int32 scheduler_id) { VLOG(config_recoverer) << "Request simple config from Google DNS"; #if TD_EMSCRIPTEN // FIXME return ActorOwn<>(); @@ -156,40 +235,47 @@ ActorOwn<> get_simple_config_google_dns(Promise promise, const Con const int timeout = 10; const int ttl = 3; const bool prefer_ipv6 = shared_config == nullptr ? false : shared_config->get_option_boolean("prefer_ipv6"); - if (name.empty()) { - name = is_test ? "tapv2.stel.com" : "apv2.stel.com"; + if (name.empty() || true) { + name = is_test ? "tapv3.stel.com" : "apv3.stel.com"; } return ActorOwn<>(create_actor_on_scheduler( "Wget", scheduler_id, PromiseCreator::lambda([promise = std::move(promise)](Result> r_query) mutable { - promise.set_result([&]() -> Result { + promise.set_result([&]() -> Result { TRY_RESULT(http_query, std::move(r_query)); - TRY_RESULT(json, json_decode(http_query->content_)); - if (json.type() != JsonValue::Type::Object) { - return Status::Error("json error"); - } - auto &answer_object = json.get_object(); - TRY_RESULT(answer, get_json_object_field(answer_object, "Answer", JsonValue::Type::Array, false)); - auto &answer_array = answer.get_array(); - vector parts; - for (auto &v : answer_array) { - if (v.type() != JsonValue::Type::Object) { + LOG(ERROR) << *http_query; + + SimpleConfigResult res; + res.r_http_date = HttpDate::parse_http_date(http_query->get_arg("date").str()); + res.r_config = [&]() -> Result { + TRY_RESULT(json, json_decode(http_query->content_)); + if (json.type() != JsonValue::Type::Object) { return Status::Error("json error"); } - auto &data_object = v.get_object(); - TRY_RESULT(part, get_json_object_string_field(data_object, "data", false)); - parts.push_back(std::move(part)); - } - if (parts.size() != 2) { - return Status::Error("Expected data in two parts"); - } - string data; - if (parts[0].size() < parts[1].size()) { - data = parts[1] + parts[0]; - } else { - data = parts[0] + parts[1]; - } - return decode_config(data); + auto &answer_object = json.get_object(); + TRY_RESULT(answer, get_json_object_field(answer_object, "Answer", JsonValue::Type::Array, false)); + auto &answer_array = answer.get_array(); + vector parts; + for (auto &v : answer_array) { + if (v.type() != JsonValue::Type::Object) { + return Status::Error("json error"); + } + auto &data_object = v.get_object(); + TRY_RESULT(part, get_json_object_string_field(data_object, "data", false)); + parts.push_back(std::move(part)); + } + if (parts.size() != 2) { + return Status::Error("Expected data in two parts"); + } + string data; + if (parts[0].size() < parts[1].size()) { + data = parts[1] + parts[0]; + } else { + data = parts[0] + parts[1]; + } + return decode_config(data); + }(); + return res; }()); }), PSTRING() << "https://www.google.com/resolve?name=" << url_encode(name) << "&type=16", @@ -198,11 +284,10 @@ ActorOwn<> get_simple_config_google_dns(Promise promise, const Con #endif } -ActorOwn<> get_full_config(DcId dc_id, IPAddress ip_address, mtproto::ProxySecret secret, Promise promise) { +ActorOwn<> get_full_config(DcOption option, Promise promise) { class SessionCallback : public Session::Callback { public: - SessionCallback(ActorShared<> parent, IPAddress address, mtproto::ProxySecret secret) - : parent_(std::move(parent)), address_(std::move(address)), secret_(std::move(secret)) { + SessionCallback(ActorShared<> parent, DcOption option) : parent_(std::move(parent)), option_(std::move(option)) { } void on_failed() final { } @@ -211,10 +296,14 @@ ActorOwn<> get_full_config(DcId dc_id, IPAddress ip_address, mtproto::ProxySecre void request_raw_connection(unique_ptr auth_data, Promise> promise) final { request_raw_connection_cnt_++; - VLOG(config_recoverer) << "Request full config from " << address_ << ", try = " << request_raw_connection_cnt_; + VLOG(config_recoverer) << "Request full config from " << option_.get_ip_address() + << ", try = " << request_raw_connection_cnt_; if (request_raw_connection_cnt_ <= 2) { - send_closure(G()->connection_creator(), &ConnectionCreator::request_raw_connection_by_ip, address_, - mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, 0, secret_}, std::move(promise)); + send_closure(G()->connection_creator(), &ConnectionCreator::request_raw_connection_by_ip, + option_.get_ip_address(), + mtproto::TransportType{mtproto::TransportType::ObfuscatedTcp, + narrow_cast(option_.get_dc_id().get_raw_id()), option_.get_secret()}, + std::move(promise)); } else { // Delay all queries except first forever delay_forever_.push_back(std::move(promise)); @@ -229,8 +318,7 @@ ActorOwn<> get_full_config(DcId dc_id, IPAddress ip_address, mtproto::ProxySecre private: ActorShared<> parent_; - IPAddress address_; - mtproto::ProxySecret secret_; + DcOption option_; size_t request_raw_connection_cnt_{0}; std::vector>> delay_forever_; }; @@ -311,17 +399,16 @@ ActorOwn<> get_full_config(DcId dc_id, IPAddress ip_address, mtproto::ProxySecre class GetConfigActor : public NetQueryCallback { public: - GetConfigActor(DcId dc_id, IPAddress ip_address, mtproto::ProxySecret secret, Promise promise) - : dc_id_(dc_id), ip_address_(std::move(ip_address)), secret_(std::move(secret)), promise_(std::move(promise)) { + GetConfigActor(DcOption option, Promise promise) + : option_(std::move(option)), promise_(std::move(promise)) { } private: void start_up() override { - auto session_callback = - make_unique(actor_shared(this, 1), std::move(ip_address_), std::move(secret_)); + auto auth_data = std::make_shared(option_.get_dc_id()); + int32 int_dc_id = option_.get_dc_id().get_raw_id(); + auto session_callback = make_unique(actor_shared(this, 1), std::move(option_)); - auto auth_data = std::make_shared(dc_id_); - int32 int_dc_id = dc_id_.get_raw_id(); if (G()->is_test_dc()) { int_dc_id += 10000; } @@ -357,15 +444,12 @@ ActorOwn<> get_full_config(DcId dc_id, IPAddress ip_address, mtproto::ProxySecre session_.reset(); } - DcId dc_id_; - IPAddress ip_address_; + DcOption option_; ActorOwn session_; - mtproto::ProxySecret secret_; Promise promise_; }; - return ActorOwn<>(create_actor("GetConfigActor", dc_id, std::move(ip_address), std::move(secret), - std::move(promise))); + return ActorOwn<>(create_actor("GetConfigActor", option, std::move(promise))); } class ConfigRecoverer : public Actor { @@ -434,9 +518,31 @@ class ConfigRecoverer : public Actor { return found; } - void on_simple_config(Result r_simple_config, bool dummy) { + void on_simple_config(Result r_simple_config_result, bool dummy) { simple_config_query_.reset(); dc_options_i_ = 0; + + SimpleConfigResult cfg; + if (r_simple_config_result.is_error()) { + cfg.r_http_date = r_simple_config_result.error().clone(); + cfg.r_config = r_simple_config_result.move_as_error(); + } else { + cfg = r_simple_config_result.move_as_ok(); + } + + if (cfg.r_http_date.is_ok() && (date_option_i_ == 0 || cfg.r_config.is_error())) { + G()->update_dns_time_difference(cfg.r_http_date.ok() - Time::now()); + } else if (cfg.r_config.is_ok()) { + G()->update_dns_time_difference(cfg.r_config.ok()->date_ - Time::now()); + } + date_option_i_ = (date_option_i_ + 1) % 2; + + do_on_simple_config(std::move(cfg.r_config)); + update_dc_options(); + loop(); + } + + void do_on_simple_config(Result r_simple_config) { if (r_simple_config.is_ok()) { auto config = r_simple_config.move_as_ok(); VLOG(config_recoverer) << "Receive raw " << to_string(config); @@ -460,7 +566,6 @@ class ConfigRecoverer : public Actor { VLOG(config_recoverer) << "Config has expired at " << config->expires_; } - G()->update_dns_time_difference(config->date_ - Time::now()); simple_config_expires_at_ = get_config_expire_time(); simple_config_at_ = Time::now_cached(); for (size_t i = 1; i < simple_config_.dc_options.size(); i++) { @@ -471,8 +576,6 @@ class ConfigRecoverer : public Actor { simple_config_ = DcOptions(); simple_config_expires_at_ = get_failed_config_expire_time(); } - update_dc_options(); - loop(); } void on_full_config(Result r_full_config, bool dummy) { @@ -526,6 +629,8 @@ class ConfigRecoverer : public Actor { double dc_options_at_{0}; size_t dc_options_i_; + size_t date_option_i_{0}; + FullConfig full_config_; double full_config_expires_at_{0}; ActorOwn<> full_config_query_; @@ -593,9 +698,10 @@ class ConfigRecoverer : public Actor { if (need_simple_config) { ref_cnt_++; VLOG(config_recoverer) << "ASK SIMPLE CONFIG"; - auto promise = PromiseCreator::lambda([actor_id = actor_shared(this)](Result r_simple_config) { - send_closure(actor_id, &ConfigRecoverer::on_simple_config, std::move(r_simple_config), false); - }); + auto promise = + PromiseCreator::lambda([actor_id = actor_shared(this)](Result r_simple_config) { + send_closure(actor_id, &ConfigRecoverer::on_simple_config, std::move(r_simple_config), false); + }); auto get_simple_config = [&]() { switch (simple_config_turn_ % 3) { case 1: @@ -614,12 +720,11 @@ class ConfigRecoverer : public Actor { if (need_full_config) { ref_cnt_++; VLOG(config_recoverer) << "ASK FULL CONFIG"; - full_config_query_ = get_full_config( - dc_options_.dc_options[dc_options_i_].get_dc_id(), dc_options_.dc_options[dc_options_i_].get_ip_address(), - dc_options_.dc_options[dc_options_i_].get_secret(), - PromiseCreator::lambda([actor_id = actor_shared(this)](Result r_full_config) { - send_closure(actor_id, &ConfigRecoverer::on_full_config, std::move(r_full_config), false); - })); + full_config_query_ = + get_full_config(dc_options_.dc_options[dc_options_i_], + PromiseCreator::lambda([actor_id = actor_shared(this)](Result r_full_config) { + send_closure(actor_id, &ConfigRecoverer::on_full_config, std::move(r_full_config), false); + })); dc_options_i_ = (dc_options_i_ + 1) % dc_options_.dc_options.size(); } diff --git a/td/telegram/ConfigManager.h b/td/telegram/ConfigManager.h index 4aef5cca..a0a5c2b8 100644 --- a/td/telegram/ConfigManager.h +++ b/td/telegram/ConfigManager.h @@ -28,14 +28,34 @@ extern int VERBOSITY_NAME(config_recoverer); class ConfigShared; using SimpleConfig = tl_object_ptr; +struct SimpleConfigResult { + Result r_config; + Result r_http_date; +}; Result decode_config(Slice input); -ActorOwn<> get_simple_config_azure(Promise promise, const ConfigShared *shared_config, bool is_test, +ActorOwn<> get_simple_config_azure(Promise promise, const ConfigShared *shared_config, bool is_test, int32 scheduler_id); -ActorOwn<> get_simple_config_google_dns(Promise promise, const ConfigShared *shared_config, bool is_test, - int32 scheduler_id); +ActorOwn<> get_simple_config_google_dns(Promise promise, const ConfigShared *shared_config, + bool is_test, int32 scheduler_id); + +class HttpDate { + public: + static bool is_leap(int year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + static int32 days_in_month(int year, int month) { + static int cnt[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + return cnt[month - 1] + (month == 2 && is_leap(year)); + } + static int64 seconds_in_day() { + return 24 * 60 * 60; + } + static Result to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second); + static Result parse_http_date(std::string slice); +}; using FullConfig = tl_object_ptr; diff --git a/td/telegram/net/ConnectionCreator.cpp b/td/telegram/net/ConnectionCreator.cpp index a505900c..c406cbdd 100644 --- a/td/telegram/net/ConnectionCreator.cpp +++ b/td/telegram/net/ConnectionCreator.cpp @@ -753,6 +753,9 @@ void ConnectionCreator::request_raw_connection_by_ip(IPAddress ip_address, mtpro }; auto token = next_token(); auto callback = td::make_unique(std::move(socket_fd_promise)); + LOG(INFO) << "Tls in ConfigRecoverer " << ip_address << " " << transport_type.secret.get_domain() << " " + << transport_type.secret.emulate_tls() << " " << transport_type.secret.get_proxy_secret().size() + << transport_type.secret.get_encoded_secret(); children_[token] = {false, create_actor( "TlsInit", std::move(socket_fd), ip_address, transport_type.secret.get_domain(), transport_type.secret.get_proxy_secret().str(), std::move(callback), @@ -984,7 +987,8 @@ void ConnectionCreator::client_loop(ClientInfo &client) { bool was_connected_{false}; unique_ptr stats_callback_; }; - LOG(INFO) << "Start " << (proxy.use_socks5_proxy() ? "Socks5" : "HTTP") << ": " << extra.debug_str; + LOG(INFO) << "Start " << (proxy.use_socks5_proxy() ? "Socks5" : (proxy.use_http_tcp_proxy() ? "HTTP" : "Tls")) + << ": " << extra.debug_str; auto token = next_token(); auto callback = td::make_unique(std::move(promise), std::move(stats_callback)); if (proxy.use_socks5_proxy()) { diff --git a/test/mtproto.cpp b/test/mtproto.cpp index 5ace4162..f43a0187 100644 --- a/test/mtproto.cpp +++ b/test/mtproto.cpp @@ -121,105 +121,17 @@ TEST(Mtproto, GetHostByNameActor) { sched.finish(); } -class Date { - public: - static bool is_leap(int year) { - return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); - } - static int32 days_in_month(int year, int month) { - int cnt[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - return cnt[month - 1] + (month == 2 && is_leap(year)); - } - static int64 seconds_in_day() { - return 24 * 60 * 60; - } - static Result to_unix_time(int32 year, int32 month, int32 day, int32 hour, int32 minute, int32 second) { - int64 res = 0; - if (year < 1970 || year > 2037) { - return td::Status::Error("invalid year"); - } - if (month < 1 || month > 12) { - return td::Status::Error("invalid month"); - } - if (day < 1 || day > days_in_month(year, month)) { - return td::Status::Error("invalid day"); - } - if (hour < 0 || hour > 24) { // is hour == 24 possible? - return td::Status::Error("invalid hour"); - } - if (minute < 0 || minute > 60) { - return td::Status::Error("invalid minute"); - } - if (second < 0 || second > 60) { - return td::Status::Error("invalid second"); - } - for (int y = 1970; y < year; y++) { - res += (is_leap(y) + 365) * seconds_in_day(); - } - for (int m = 1; m < month; m++) { - res += days_in_month(year, m) * seconds_in_day(); - } - res += (day - 1) * seconds_in_day(); - res += hour * 60 * 60; - res += minute * 60; - res += second; - return res; - } -}; - TEST(Time, to_unix_time) { - ASSERT_EQ(0, Date::to_unix_time(1970, 1, 1, 0, 0, 0).move_as_ok()); - ASSERT_EQ(60 * 60 + 60 + 1, Date::to_unix_time(1970, 1, 1, 1, 1, 1).move_as_ok()); - ASSERT_EQ(24 * 60 * 60, Date::to_unix_time(1970, 1, 2, 0, 0, 0).move_as_ok()); - ASSERT_EQ(31 * 24 * 60 * 60, Date::to_unix_time(1970, 2, 1, 0, 0, 0).move_as_ok()); - ASSERT_EQ(365 * 24 * 60 * 60, Date::to_unix_time(1971, 1, 1, 0, 0, 0).move_as_ok()); - ASSERT_EQ(1562780559, Date::to_unix_time(2019, 7, 10, 17, 42, 39).move_as_ok()); -} - -Result parse_http_date(std::string slice) { - td::Parser p(slice); - p.read_till(','); // ignore week day - p.skip(','); - p.skip_whitespaces(); - TRY_RESULT(day, to_integer_safe(p.read_word())); - auto month_name = p.read_word(); - to_lower_inplace(month_name); - TRY_RESULT(year, to_integer_safe(p.read_word())); - p.skip_whitespaces(); - p.skip_nofail('0'); - TRY_RESULT(hour, to_integer_safe(p.read_till(':'))); - p.skip(':'); - p.skip_nofail('0'); - TRY_RESULT(minute, to_integer_safe(p.read_till(':'))); - p.skip(':'); - p.skip_nofail('0'); - TRY_RESULT(second, to_integer_safe(p.read_word())); - auto gmt = p.read_word(); - TRY_STATUS(std::move(p.status())); - if (gmt != "GMT") { - return Status::Error("timezone must be GMT"); - } - - Slice month_names[12] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"}; - - int month = 0; - - for (int m = 1; m <= 12; m++) { - if (month_names[m - 1] == month_name) { - month = m; - break; - } - } - - if (month == 0) { - return Status::Error("Unknown month name"); - } - - return Date::to_unix_time(year, month, day, hour, minute, second); + ASSERT_EQ(0, HttpDate::to_unix_time(1970, 1, 1, 0, 0, 0).move_as_ok()); + ASSERT_EQ(60 * 60 + 60 + 1, HttpDate::to_unix_time(1970, 1, 1, 1, 1, 1).move_as_ok()); + ASSERT_EQ(24 * 60 * 60, HttpDate::to_unix_time(1970, 1, 2, 0, 0, 0).move_as_ok()); + ASSERT_EQ(31 * 24 * 60 * 60, HttpDate::to_unix_time(1970, 2, 1, 0, 0, 0).move_as_ok()); + ASSERT_EQ(365 * 24 * 60 * 60, HttpDate::to_unix_time(1971, 1, 1, 0, 0, 0).move_as_ok()); + ASSERT_EQ(1562780559, HttpDate::to_unix_time(2019, 7, 10, 17, 42, 39).move_as_ok()); } TEST(Time, parse_http_date) { - ASSERT_EQ(784887151, parse_http_date("Tue, 15 Nov 1994 08:12:31 GMT").move_as_ok()); + ASSERT_EQ(784887151, HttpDate::parse_http_date("Tue, 15 Nov 1994 08:12:31 GMT").move_as_ok()); } TEST(Mtproto, config) {