From 7de9a0c15d78ac743c6813576bc66646fbf39d07 Mon Sep 17 00:00:00 2001 From: levlam Date: Mon, 14 May 2018 22:00:38 +0300 Subject: [PATCH] New Proxy API. GitOrigin-RevId: 2f96b8d1732c25e10a7568c6cf2dbbe26a150e4a --- td/generate/scheme/td_api.tl | 71 ++++--- td/generate/scheme/td_api.tlo | Bin 124968 -> 125752 bytes td/telegram/AuthManager.cpp | 3 + td/telegram/MessagesManager.cpp | 6 +- td/telegram/Td.cpp | 41 +++- td/telegram/Td.h | 12 +- td/telegram/cli.cpp | 38 ++-- td/telegram/net/ConnectionCreator.cpp | 260 ++++++++++++++++++++++---- td/telegram/net/ConnectionCreator.h | 57 ++---- td/telegram/net/Session.cpp | 18 +- tdactor/td/actor/impl/Scheduler.cpp | 2 +- 11 files changed, 375 insertions(+), 133 deletions(-) diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index f9df66acf..c3512822b 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -117,7 +117,7 @@ temporaryPasswordState has_password:Bool valid_for:int32 = TemporaryPasswordStat localFile path:string can_be_downloaded:Bool can_be_deleted:Bool is_downloading_active:Bool is_downloading_completed:Bool downloaded_prefix_size:int32 downloaded_size:int32 = LocalFile; //@description Represents a remote file -//@id Remote file identifier, may be empty. Can be used across application restarts or even from other devices for the current user. If the ID starts with "http://" or "https://", it represents the HTTP URL of the file. TDLib is currently unable to download files if only their URL is known. +//@id Remote file identifier; may be empty. Can be used across application restarts or even from other devices for the current user. If the ID starts with "http://" or "https://", it represents the HTTP URL of the file. TDLib is currently unable to download files if only their URL is known. //-If downloadFile is called on such a file or if it is sent to a secret chat, TDLib starts a file generation process by sending updateFileGenerationStart to the client with the HTTP URL in the original_path and "#url#" as the conversion string. Clients should generate the file by downloading it to the specified location //@is_uploading_active True, if the file is currently being uploaded (or a remote copy is being generated by some other means) //@is_uploading_completed True, if a remote copy is fully available @@ -144,7 +144,7 @@ inputFileRemote id:string = InputFile; //@description A file defined by a local path @path Local path to the file inputFileLocal path:string = InputFile; -//@description A file generated by the client @original_path Local path to a file from which the file is generated, may be empty if there is no such file @conversion String specifying the conversion applied to the original file; should be persistent across application restarts @expected_size Expected size of the generated file; 0 if unknown +//@description A file generated by the client @original_path Local path to a file from which the file is generated; may be empty if there is no such file @conversion String specifying the conversion applied to the original file; should be persistent across application restarts @expected_size Expected size of the generated file; 0 if unknown inputFileGenerated original_path:string conversion:string expected_size:int32 = InputFile; @@ -1119,7 +1119,7 @@ messageGameScore game_message_id:int53 game_id:int64 score:int32 = MessageConten messagePaymentSuccessful invoice_message_id:int53 currency:string total_amount:int53 = MessageContent; //@description A payment has been completed; for bots only @invoice_message_id Identifier of the message with the corresponding invoice; can be an identifier of a deleted message @currency Currency for price of the product -//@total_amount Total price for the product, in the minimal quantity of the currency @invoice_payload Invoice payload @shipping_option_id Identifier of the shipping option chosen by the user, may be empty if not applicable @order_info Information about the order; may be null +//@total_amount Total price for the product, in the minimal quantity of the currency @invoice_payload Invoice payload @shipping_option_id Identifier of the shipping option chosen by the user; may be empty if not applicable @order_info Information about the order; may be null //@telegram_payment_charge_id Telegram payment identifier @provider_payment_charge_id Provider payment identifier messagePaymentSuccessfulBot invoice_message_id:int53 currency:string total_amount:int53 invoice_payload:bytes shipping_option_id:string order_info:orderInfo telegram_payment_charge_id:string provider_payment_charge_id:string = MessageContent; @@ -1648,38 +1648,38 @@ chatEventLogFilters message_edits:Bool message_deletions:Bool message_pins:Bool //@class DeviceToken @description Represents a data needed to subscribe for push notifications. To use specific push notification service, you must specify the correct application platform and upload valid server authentication data at https://my.telegram.org -//@description A token for Google Cloud Messaging @token Device registration token, may be empty to de-register a device +//@description A token for Google Cloud Messaging @token Device registration token; may be empty to de-register a device deviceTokenGoogleCloudMessaging token:string = DeviceToken; -//@description A token for Apple Push Notification service @device_token Device token, may be empty to de-register a device @is_app_sandbox True, if App Sandbox is enabled +//@description A token for Apple Push Notification service @device_token Device token; may be empty to de-register a device @is_app_sandbox True, if App Sandbox is enabled deviceTokenApplePush device_token:string is_app_sandbox:Bool = DeviceToken; -//@description A token for Apple Push Notification service VoIP notifications @device_token Device token, may be empty to de-register a device @is_app_sandbox True, if App Sandbox is enabled +//@description A token for Apple Push Notification service VoIP notifications @device_token Device token; may be empty to de-register a device @is_app_sandbox True, if App Sandbox is enabled deviceTokenApplePushVoIP device_token:string is_app_sandbox:Bool = DeviceToken; -//@description A token for Windows Push Notification Services @access_token The access token that will be used to send notifications, may be empty to de-register a device +//@description A token for Windows Push Notification Services @access_token The access token that will be used to send notifications; may be empty to de-register a device deviceTokenWindowsPush access_token:string = DeviceToken; -//@description A token for Microsoft Push Notification Service @channel_uri Push notification channel URI, may be empty to de-register a device +//@description A token for Microsoft Push Notification Service @channel_uri Push notification channel URI; may be empty to de-register a device deviceTokenMicrosoftPush channel_uri:string = DeviceToken; -//@description A token for Microsoft Push Notification Service VoIP channel @channel_uri Push notification channel URI, may be empty to de-register a device +//@description A token for Microsoft Push Notification Service VoIP channel @channel_uri Push notification channel URI; may be empty to de-register a device deviceTokenMicrosoftPushVoIP channel_uri:string = DeviceToken; -//@description A token for web Push API @endpoint Absolute URL exposed by the push service where the application server can send push messages, may be empty to de-register a device +//@description A token for web Push API @endpoint Absolute URL exposed by the push service where the application server can send push messages; may be empty to de-register a device //@p256dh_base64url Base64url-encoded P-256 elliptic curve Diffie-Hellman public key @auth_base64url Base64url-encoded authentication secret deviceTokenWebPush endpoint:string p256dh_base64url:string auth_base64url:string = DeviceToken; -//@description A token for Simple Push API for Firefox OS @endpoint Absolute URL exposed by the push service where the application server can send push messages, may be empty to de-register a device +//@description A token for Simple Push API for Firefox OS @endpoint Absolute URL exposed by the push service where the application server can send push messages; may be empty to de-register a device deviceTokenSimplePush endpoint:string = DeviceToken; -//@description A token for Ubuntu Push Client service @token Token, may be empty to de-register a device +//@description A token for Ubuntu Push Client service @token Token; may be empty to de-register a device deviceTokenUbuntuPush token:string = DeviceToken; -//@description A token for BlackBerry Push Service @token Token, may be empty to de-register a device +//@description A token for BlackBerry Push Service @token Token; may be empty to de-register a device deviceTokenBlackBerryPush token:string = DeviceToken; -//@description A token for Tizen Push Service @reg_id Push service registration identifier, may be empty to de-register a device +//@description A token for Tizen Push Service @reg_id Push service registration identifier; may be empty to de-register a device deviceTokenTizenPush reg_id:string = DeviceToken; @@ -1979,6 +1979,9 @@ count count:int32 = Count; //@description Contains some text @text Text text text:string = Text; +//@description Contains a value representing number of seconds @seconds Number of seconds +seconds seconds:double = Seconds; + //@description Contains information about a tg:// deep link @text Text to be shown to the user @need_update_application True, if user should be asked to update the application deepLinkInfo text:formattedText need_update_application:Bool = DeepLinkInfo; @@ -1993,16 +1996,20 @@ textParseModeMarkdown = TextParseMode; textParseModeHTML = TextParseMode; -//@class Proxy @description Contains information about a proxy server +//@class ProxyType @description Describes the type of the proxy server -//@description An empty proxy server -proxyEmpty = Proxy; +//@description A SOCKS5 proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty +proxyTypeSocks5 username:string password:string = ProxyType; -//@description A SOCKS5 proxy server @server Proxy server IP address @port Proxy server port @username Username for logging in @password Password for logging in -proxySocks5 server:string port:int32 username:string password:string = Proxy; +//@description An MTProto proxy server @secret Proxy's secret in hexadecimal encoding +proxyTypeMtproto secret:string = ProxyType; -//@description An MTProro proxy server @server Proxy server IP address @port Proxy server port @secret Server's secret -proxyMtproto server:string port:int32 secret:string = Proxy; + +//@description Contains information about a proxy server @id Unique proxy identifier @server Proxy server IP address @port Proxy server port @last_used_date Point in time (Unix timestamp) when the proxy was last used; 0 if never @is_enabled True, if the proxy is enabled now @type Type of a proxy +proxy id:int32 server:string port:int32 last_used_date:int32 is_enabled:Bool type:ProxyType = Proxy; + +//@description Represents a list of proxy servers @proxies List of proxy servers +proxies proxies:vector = Proxies; //@description Describes a sticker that should be added to a sticker set @png_sticker PNG image with the sticker; must be up to 512 kB in size and fit in a 512x512 square @emojis Emoji corresponding to the sticker @mask_position For masks, position where the mask should be placed; may be null @@ -2129,7 +2136,7 @@ updateFile file:file = Update; //@description The file generation process needs to be started by the client //@generation_id Unique identifier for the generation process -//@original_path The path to a file from which a new file is generated, may be empty +//@original_path The path to a file from which a new file is generated; may be empty //@destination_path The path to a file that should be created and where the new file should be generated //@conversion String specifying the conversion applied to the original file. If conversion is "#url#" than original_path contains a HTTP/HTTPS URL of a file, which should be downloaded by the client updateFileGenerationStart generation_id:int64 original_path:string destination_path:string conversion:string = Update; @@ -3153,11 +3160,23 @@ getTermsOfService = Text; getDeepLinkInfo link:string = DeepLinkInfo; -//@description Sets the proxy server for network requests. Can be called before authorization @proxy Proxy server to use. Specify null to remove the proxy server -setProxy proxy:Proxy = Ok; +//@description Adds a proxy server for network requests. Can be called before authorization. Can be called before initialization @server Proxy server IP address @port Proxy server port @enable True, if the proxy should be enabled @type Type of a proxy +addProxy server:string port:int32 enable:Bool type:ProxyType = Proxy; -//@description Returns the proxy that is currently set up. Can be called before authorization -getProxy = Proxy; +//@description Enables a proxy. Only one proxy can be enabled at a time. Can be called before authorization @proxy_id The proxy identifier +enableProxy proxy_id:int32 = Ok; + +//@description Disables currently enabled proxy. Can be called before authorization +disableProxy = Ok; + +//@description Removes a proxy server. Can be called before authorization @proxy_id The proxy identifier +removeProxy proxy_id:int32 = Ok; + +//@description Returns list of proxies that are currently set up. Can be called before authorization +getProxies = Proxies; + +//@description Computes time needed to receive a response from a Telegram server through the proxy. Can be called before authorization @proxy_id The proxy identifier +pingProxy proxy_id:int32 = Seconds; //@description Does nothing; for testing only diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index a08387ebfc23125f1a3113d4586559802cbbebc3..250fd44c2a45d9af9e90c9dd1c20abb6efcc1bcd 100644 GIT binary patch delta 762 zcmZ2+fqlm{cHT#`^{p77;J`-S$09sW<9glM1B&u1GE<8u8!$_76<4q4W(5gUPF}|( z#p4$%;J^vyhEz^IASkn0M6`mDr|+qmC3|pca(-S4Sl8wru?3urZku%#?q5{_8(9D` z62!iFOvr~3XsPtG`4ZGB$lKyOm^5S$(30gpPHALl#`k=xn4tZGS4~z zN00|#2Er@>83YppS+d#b^C^C?B_WjssloZl*~O+HeIS=JP5+qBs6P3?HyuM{qd?mJ z%0KQBfNS$D0WwSSf#$-rGEV-OB0jm`y9f(NWOK)N16~1e$Q46E4#b|`aEj4@1;pCE zhJ|rWG^51!$OOi6X2~0S4heH4rlbTw{0{a!$UQ8RCBJ*HL3|1Gqy)0dkUhEmNHU|0 zlwgziE9{!nnuH^{A?AVQGWWf`f*fTWKyOu4#%HEX z{%9pX{lYay!R=dH8UKrF7&C|c;z~~~0qFv!9tK!kgF+VO2$0zJJF^+z2xx9}n0=J9 zATuu=YzqSe$j>n(T|`1acRp=H{0{9FO0D2a*{DwqIG#$fLg9RRB`rnp;p($pF%F^O%qi$ebc)HXRPzwu2xsn88=&xMC!(pAX{( z>j}0y~H_}zW_gm6aI>2nV-N>4Ya0J3W5FskdKn{ZZkMJ*551m6-MRgw=h z9%c$N$dt($sRENH{19OQiEdu;!+>|Y1{>poXhxCkCleXVnYVvQWfYLwZqvYcg?)KO z2cwwAp^fK*IMP!~0wCUn8;9td_db()->get_binlog_pmc()->get("auth_state"); + if (data.empty()) { + return false; + } DbState db_state; auto status = log_event_parse(db_state, data); if (status.is_error()) { diff --git a/td/telegram/MessagesManager.cpp b/td/telegram/MessagesManager.cpp index 04f3cba23..2a1d586ad 100644 --- a/td/telegram/MessagesManager.cpp +++ b/td/telegram/MessagesManager.cpp @@ -25903,7 +25903,11 @@ void MessagesManager::set_promoted_dialog_id(DialogId dialog_id) { } if (G()->parameters().use_message_db) { - G()->td_db()->get_binlog_pmc()->set("promoted_dialog_id", to_string(promoted_dialog_id_.get())); + if (promoted_dialog_id_.is_valid()) { + G()->td_db()->get_binlog_pmc()->set("promoted_dialog_id", to_string(promoted_dialog_id_.get())); + } else { + G()->td_db()->get_binlog_pmc()->erase("promoted_dialog_id"); + } LOG(INFO) << "Save promoted " << promoted_dialog_id_; } diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index f52ee2375..d22fcbcc3 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -7096,22 +7096,43 @@ void Td::on_request(uint64 id, td_api::getDeepLinkInfo &request) { create_handler(std::move(promise))->send(request.link_); } -void Td::on_request(uint64 id, const td_api::getProxy &request) { +void Td::on_request(uint64 id, td_api::addProxy &request) { + CLEAN_INPUT_STRING(request.server_); CREATE_REQUEST_PROMISE(promise); - auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { + send_closure(G()->connection_creator(), &ConnectionCreator::add_proxy, std::move(request.server_), request.port_, + request.enable_, std::move(request.type_), std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::enableProxy &request) { + CREATE_OK_REQUEST_PROMISE(promise); + send_closure(G()->connection_creator(), &ConnectionCreator::enable_proxy, request.proxy_id_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::disableProxy &request) { + CREATE_OK_REQUEST_PROMISE(promise); + send_closure(G()->connection_creator(), &ConnectionCreator::disable_proxy, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::removeProxy &request) { + CREATE_OK_REQUEST_PROMISE(promise); + send_closure(G()->connection_creator(), &ConnectionCreator::remove_proxy, request.proxy_id_, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::getProxies &request) { + CREATE_REQUEST_PROMISE(promise); + send_closure(G()->connection_creator(), &ConnectionCreator::get_proxies, std::move(promise)); +} + +void Td::on_request(uint64 id, const td_api::pingProxy &request) { + CREATE_REQUEST_PROMISE(promise); + auto query_promise = PromiseCreator::lambda([promise = std::move(promise)](Result result) mutable { if (result.is_error()) { promise.set_error(result.move_as_error()); } else { - promise.set_value(result.move_as_ok().as_td_api()); + promise.set_value(make_tl_object(result.move_as_ok())); } }); - send_closure(G()->connection_creator(), &ConnectionCreator::get_proxy, std::move(query_promise)); -} - -void Td::on_request(uint64 id, const td_api::setProxy &request) { - CREATE_OK_REQUEST_PROMISE(promise); - send_closure(G()->connection_creator(), &ConnectionCreator::set_proxy, Proxy::from_td_api(request.proxy_)); - promise.set_value(Unit()); + send_closure(G()->connection_creator(), &ConnectionCreator::ping_proxy, request.proxy_id_, std::move(query_promise)); } void Td::on_request(uint64 id, const td_api::getTextEntities &request) { diff --git a/td/telegram/Td.h b/td/telegram/Td.h index 64cb9bcb3..08ed9865a 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -800,9 +800,17 @@ class Td final : public NetQueryCallback { void on_request(uint64 id, td_api::getDeepLinkInfo &request); - void on_request(uint64 id, const td_api::getProxy &request); + void on_request(uint64 id, td_api::addProxy &request); - void on_request(uint64 id, const td_api::setProxy &request); + void on_request(uint64 id, const td_api::enableProxy &request); + + void on_request(uint64 id, const td_api::disableProxy &request); + + void on_request(uint64 id, const td_api::removeProxy &request); + + void on_request(uint64 id, const td_api::getProxies &request); + + void on_request(uint64 id, const td_api::pingProxy &request); void on_request(uint64 id, const td_api::getTextEntities &request); diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 0a897ea8d..d120557f8 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -437,10 +437,6 @@ class CliClient final : public Actor { return to_integer(trim(std::move(str))); } - static int32 as_call_id(string str) { - return to_integer(trim(std::move(str))); - } - static td_api::object_ptr as_input_file_id(string str) { return make_tl_object(as_file_id(str)); } @@ -473,6 +469,14 @@ class CliClient final : public Actor { return as_local_file(str); } + static int32 as_call_id(string str) { + return to_integer(trim(std::move(str))); + } + + static int32 as_proxy_id(string str) { + return to_integer(trim(std::move(str))); + } + static tl_object_ptr as_location(string latitude, string longitude) { return make_tl_object(to_double(latitude), to_double(longitude)); } @@ -3052,9 +3056,13 @@ class CliClient final : public Actor { as_message_ids(message_ids))); } else if (op == "gdiff") { send_request(make_tl_object()); - } else if (op == "cproxy") { - send_request(make_tl_object(make_tl_object())); - } else if (op == "sproxy") { + } else if (op == "dproxy") { + send_request(make_tl_object()); + } else if (op == "eproxy") { + send_request(make_tl_object(as_proxy_id(args))); + } else if (op == "rproxy") { + send_request(make_tl_object(as_proxy_id(args))); + } else if (op == "aproxy" || op == "aeproxy") { string server; string port; string user; @@ -3062,15 +3070,17 @@ class CliClient final : public Actor { std::tie(server, args) = split(args); std::tie(port, args) = split(args); std::tie(user, password) = split(args); + td_api::object_ptr type; if (!user.empty() && password.empty()) { - send_request(make_tl_object( - make_tl_object(server, to_integer(port), user))); + type = make_tl_object(user); } else { - send_request(make_tl_object( - make_tl_object(server, to_integer(port), user, password))); + type = make_tl_object(user, password); } - } else if (op == "gproxy") { - send_request(make_tl_object()); + send_request(make_tl_object(server, to_integer(port), op == "aeproxy", std::move(type))); + } else if (op == "gproxy" || op == "gproxies") { + send_request(make_tl_object()); + } else if (op == "pproxy") { + send_request(make_tl_object(as_proxy_id(args))); } else if (op == "touch") { auto r_fd = FileFd::open(args, FileFd::Read | FileFd::Write); if (r_fd.is_error()) { @@ -3083,7 +3093,7 @@ class CliClient final : public Actor { fd.write("a").ignore(); fd.seek(size).ignore(); fd.truncate_to_current_position(size).ignore(); - } else if (op == "SetVerbosity") { + } else if (op == "SetVerbosity" || op == "SV") { td::Log::set_verbosity_level(to_integer(args)); } else if (op == "q" || op == "Quit") { quit(); diff --git a/td/telegram/net/ConnectionCreator.cpp b/td/telegram/net/ConnectionCreator.cpp index 0891a6625..bd07d5722 100644 --- a/td/telegram/net/ConnectionCreator.cpp +++ b/td/telegram/net/ConnectionCreator.cpp @@ -32,6 +32,7 @@ #include "td/utils/tl_helpers.h" #include +#include namespace td { @@ -215,35 +216,143 @@ void ConnectionCreator::set_net_stats_callback(std::shared_ptr media_net_stats_callback_ = std::move(media_callback); } -void ConnectionCreator::set_proxy(Proxy proxy) { - set_proxy_impl(std::move(proxy), false); - loop(); +void ConnectionCreator::add_proxy(string server, int32 port, bool enable, + td_api::object_ptr proxy_type, + Promise> promise) { + if (proxy_type == nullptr) { + return promise.set_error(Status::Error(400, "Proxy type should not be empty")); + } + if (server.empty()) { + return promise.set_error(Status::Error(400, "Server name can't be empty")); + } + if (port <= 0 || port > 65535) { + return promise.set_error(Status::Error(400, "Wrong port number")); + } + + Proxy new_proxy; + switch (proxy_type->get_id()) { + case td_api::proxyTypeSocks5::ID: { + auto type = td_api::move_object_as(proxy_type); + new_proxy = Proxy::socks5(server, port, type->username_, type->password_); + break; + } + case td_api::proxyTypeMtproto::ID: { + auto type = td_api::move_object_as(proxy_type); + if (type->secret_.size() != 32 || hex_decode(type->secret_).is_error()) { + return promise.set_error(Status::Error(400, "Wrong server secret")); + } + new_proxy = Proxy::mtproto(server, port, type->secret_); + break; + } + default: + UNREACHABLE(); + } + + auto proxy_id = [&] { + for (auto &proxy : proxies_) { + if (proxy.second == new_proxy) { + return proxy.first; + } + } + + CHECK(max_proxy_id_ >= 2); + auto proxy_id = max_proxy_id_++; + G()->td_db()->get_binlog_pmc()->set("proxy_max_id", to_string(max_proxy_id_)); + CHECK(proxies_.count(proxy_id) == 0); + proxies_.emplace(proxy_id, std::move(new_proxy)); + G()->td_db()->get_binlog_pmc()->set(get_proxy_database_key(proxy_id), + log_event_store(proxies_[proxy_id]).as_slice().str()); + }(); + if (enable) { + enable_proxy_impl(proxy_id); + } + promise.set_value(get_proxy_object(proxy_id)); } -void ConnectionCreator::set_proxy_impl(Proxy proxy, bool from_db) { - auto have_proxy = proxy.type() != Proxy::Type::None; - if (proxy_ == proxy) { - if (!have_proxy) { - on_get_proxy_info(make_tl_object(0)); - } +void ConnectionCreator::enable_proxy(int32 proxy_id, Promise promise) { + if (proxies_.count(proxy_id) == 0) { + return promise.set_error(Status::Error(400, "Unknown proxy identifier")); + } + + enable_proxy_impl(proxy_id); + promise.set_value(Unit()); +} + +void ConnectionCreator::disable_proxy(Promise promise) { + disable_proxy_impl(); + promise.set_value(Unit()); +} + +void ConnectionCreator::remove_proxy(int32 proxy_id, Promise promise) { + if (proxies_.count(proxy_id) == 0) { + return promise.set_error(Status::Error(400, "Unknown proxy identifier")); + } + + if (proxy_id == active_proxy_id_) { + disable_proxy_impl(); + } + + proxies_.erase(proxy_id); + + G()->td_db()->get_binlog_pmc()->erase(get_proxy_database_key(proxy_id)); + G()->td_db()->get_binlog_pmc()->erase(get_proxy_used_database_key(proxy_id)); + promise.set_value(Unit()); +} + +void ConnectionCreator::get_proxies(Promise> promise) { + promise.set_value(td_api::make_object( + transform(proxies_, [this](const std::pair &proxy) { return get_proxy_object(proxy.first); }))); +} + +void ConnectionCreator::ping_proxy(int32 proxy_id, Promise promise) { + if (proxies_.count(proxy_id) == 0) { + return promise.set_error(Status::Error(400, "Unknown proxy identifier")); + } + + // TODO ping + promise.set_value(0.0); +} + +void ConnectionCreator::enable_proxy_impl(int32 proxy_id) { + CHECK(proxies_.count(proxy_id) == 1); + if (proxy_id == active_proxy_id_) { return; } - if (proxy_.type() == Proxy::Type::Mtproto || proxy.type() == Proxy::Type::Mtproto) { - G()->mtproto_header().set_proxy(proxy); + if ((active_proxy_id_ != 0 && proxies_[active_proxy_id_].type() == Proxy::Type::Mtproto) || + proxies_[proxy_id].type() == Proxy::Type::Mtproto) { + G()->mtproto_header().set_proxy(proxies_[proxy_id]); G()->net_query_dispatcher().update_mtproto_header(); } - proxy_ = std::move(proxy); + active_proxy_id_ = proxy_id; + G()->td_db()->get_binlog_pmc()->set("proxy_active_id", to_string(proxy_id)); - send_closure(G()->state_manager(), &StateManager::on_proxy, have_proxy); + on_proxy_changed(false); +} + +void ConnectionCreator::disable_proxy_impl() { + if (active_proxy_id_ == 0) { + on_get_proxy_info(make_tl_object(0)); + return; + } + CHECK(proxies_.count(active_proxy_id_) == 1); + + if (proxies_[active_proxy_id_].type() == Proxy::Type::Mtproto) { + G()->mtproto_header().set_proxy(Proxy()); + G()->net_query_dispatcher().update_mtproto_header(); + } + + active_proxy_id_ = 0; + G()->td_db()->get_binlog_pmc()->erase("proxy_active_id"); + + on_proxy_changed(false); +} + +void ConnectionCreator::on_proxy_changed(bool from_db) { + send_closure(G()->state_manager(), &StateManager::on_proxy, active_proxy_id_ != 0); if (!from_db) { - if (have_proxy) { - G()->td_db()->get_binlog_pmc()->set("proxy", log_event_store(proxy_).as_slice().str()); - } else { - G()->td_db()->get_binlog_pmc()->erase("proxy"); - } for (auto &child : children_) { child.second.reset(); } @@ -255,15 +364,47 @@ void ConnectionCreator::set_proxy_impl(Proxy proxy, bool from_db) { get_proxy_info_query_token_ = 0; get_proxy_info_timestamp_ = Timestamp(); - if (!have_proxy || !from_db) { + if (active_proxy_id_ == 0 || !from_db) { on_get_proxy_info(make_tl_object(0)); } else { schedule_get_proxy_info(0); } + + loop(); } -void ConnectionCreator::get_proxy(Promise promise) { - promise.set_value(Proxy(proxy_)); +string ConnectionCreator::get_proxy_database_key(int32 proxy_id) { + CHECK(proxy_id > 0); + if (proxy_id == 1) { + return "proxy"; + } + return PSTRING() << "proxy" << proxy_id; +} + +string ConnectionCreator::get_proxy_used_database_key(int32 proxy_id) { + CHECK(proxy_id > 0); + return PSTRING() << "proxy_used" << proxy_id; +} + +td_api::object_ptr ConnectionCreator::get_proxy_object(int32 proxy_id) const { + auto it = proxies_.find(proxy_id); + CHECK(it != proxies_.end()); + const Proxy &proxy = it->second; + td_api::object_ptr type; + switch (proxy.type()) { + case Proxy::Type::Socks5: + type = make_tl_object(proxy.user().str(), proxy.password().str()); + break; + case Proxy::Type::Mtproto: + type = make_tl_object(proxy.secret().str()); + break; + default: + UNREACHABLE(); + } + auto last_used_date_it = proxy_last_used_date_.find(proxy_id); + auto last_used_date = last_used_date_it == proxy_last_used_date_.end() ? 0 : last_used_date_it->second; + return make_tl_object(proxy_id, proxy.server().str(), proxy.port(), last_used_date, + proxy_id == active_proxy_id_, std::move(type)); } void ConnectionCreator::on_network(bool network_flag, uint32 network_generation) { @@ -319,7 +460,7 @@ void ConnectionCreator::request_raw_connection(DcId dc_id, bool allow_media_only CHECK(client.allow_media_only == allow_media_only); CHECK(client.is_media == is_media); } - VLOG(connections) << tag("client", format::as_hex(client.hash)) << " " << dc_id << " " + VLOG(connections) << "Request connection for " << tag("client", format::as_hex(client.hash)) << " to " << dc_id << " " << tag("allow_media_only", allow_media_only); client.queries.push_back(std::move(promise)); @@ -346,8 +487,10 @@ void ConnectionCreator::client_loop(ClientInfo &client) { if (close_flag_) { return; } - auto proxy_type = proxy_.type(); - bool use_proxy = proxy_type != Proxy::Type::None; + + Proxy *proxy = active_proxy_id_ == 0 ? nullptr : &proxies_[active_proxy_id_]; + auto proxy_type = proxy == nullptr ? Proxy::Type::None : proxy->type(); + bool use_proxy = proxy != nullptr; bool use_socks5_proxy = proxy_type == Proxy::Type::Socks5; bool use_mtproto_proxy = proxy_type == Proxy::Type::Mtproto; if (use_proxy && !proxy_ip_address_.is_valid()) { @@ -426,7 +569,7 @@ void ConnectionCreator::client_loop(ClientInfo &client) { TRY_RESULT(info, dc_options_set_.find_connection(dc_id, allow_media_only, use_proxy)); stat = nullptr; int16 raw_dc_id = narrow_cast(info.option->is_media_only() ? -dc_id.get_raw_id() : dc_id.get_raw_id()); - TRY_RESULT(secret, hex_decode(proxy_.secret())); + TRY_RESULT(secret, hex_decode(proxy->secret())); transport_type = {mtproto::TransportType::ObfuscatedTcp, raw_dc_id, std::move(secret)}; debug_str = PSTRING() << "Mtproto " << proxy_ip_address_ << " to DC" << raw_dc_id; @@ -526,7 +669,7 @@ void ConnectionCreator::client_loop(ClientInfo &client) { LOG(INFO) << "Start socks5: " << debug_str; auto token = next_token(); children_[token] = create_actor( - "Socks5", std::move(socket_fd), mtproto_ip, proxy_.user().str(), proxy_.password().str(), + "Socks5", std::move(socket_fd), mtproto_ip, proxy->user().str(), proxy->password().str(), std::make_unique(std::move(promise), std::move(stats_callback)), create_reference(token)); } else { ConnectionData data; @@ -542,7 +685,8 @@ void ConnectionCreator::client_create_raw_connection(Result r_co string debug_str, uint32 network_generation) { auto promise = PromiseCreator::lambda([actor_id = actor_id(this), hash, check_mode, debug_str](Result> result) mutable { - VLOG(connections) << "Ready " << debug_str << " " << tag("checked", check_mode) << tag("ok", result.is_ok()); + VLOG(connections) << "Ready connection " << (check_mode ? "(" : "(un") << "checked) " + << (result.is_ok() ? result.ok().get() : nullptr) << " " << debug_str; send_closure(std::move(actor_id), &ConnectionCreator::client_add_connection, hash, std::move(result), check_mode); }); @@ -588,6 +732,7 @@ void ConnectionCreator::client_add_connection(size_t hash, client.checking_connections--; } if (r_raw_connection.is_ok()) { + VLOG(connections) << "Add ready connection " << r_raw_connection.ok().get() << " for " << tag("client", hash); client.backoff.clear(); client.ready_connections.push_back(std::make_pair(r_raw_connection.move_as_ok(), Time::now_cached())); } @@ -652,18 +797,59 @@ void ConnectionCreator::start_up() { on_dc_options(std::move(dc_options)); } - Proxy proxy; - auto log_event_proxy = G()->td_db()->get_binlog_pmc()->get("proxy"); - if (!log_event_proxy.empty()) { - log_event_parse(proxy, log_event_proxy).ensure(); + auto proxy_info = G()->td_db()->get_binlog_pmc()->prefix_get("proxy"); + auto it = proxy_info.find("proxy_max_id"); + if (it != proxy_info.end()) { + max_proxy_id_ = to_integer(it->second); + proxy_info.erase(it); + } + it = proxy_info.find("proxy_active_id"); + if (it != proxy_info.end()) { + active_proxy_id_ = to_integer(it->second); + proxy_info.erase(it); + } + + for (auto &info : proxy_info) { + if (begins_with(info.first, "proxy_used")) { + int32 proxy_id = to_integer_safe(Slice(info.first).substr(10)).move_as_ok(); + int32 last_used = to_integer_safe(info.second).move_as_ok(); + proxy_last_used_date_[proxy_id] = last_used; + } else { + int32 proxy_id = info.first == "proxy" ? 1 : to_integer_safe(Slice(info.first).substr(5)).move_as_ok(); + CHECK(proxies_.count(proxy_id) == 0); + log_event_parse(proxies_[proxy_id], info.second).ensure(); + } + } + + if (max_proxy_id_ == 0) { + // legacy one-proxy version + max_proxy_id_ = 2; + if (!proxies_.empty()) { + CHECK(proxies_.begin()->first == 1); + active_proxy_id_ = 1; + G()->td_db()->get_binlog_pmc()->set("proxy_active_id", "1"); + } + G()->td_db()->get_binlog_pmc()->set("proxy_max_id", "2"); + } else if (max_proxy_id_ < 2) { + LOG(ERROR) << "Found wrong max_proxy_id = " << max_proxy_id_; + max_proxy_id_ = 2; + } + + if (active_proxy_id_ != 0) { + if (proxies_[active_proxy_id_].type() == Proxy::Type::Mtproto) { + G()->mtproto_header().set_proxy(proxies_[active_proxy_id_]); + G()->net_query_dispatcher().update_mtproto_header(); + } + + on_proxy_changed(true); } - set_proxy_impl(std::move(proxy), true); get_host_by_name_actor_ = create_actor_on_scheduler("GetHostByNameActor", G()->get_gc_scheduler_id(), 5 * 60 - 1, 0); ref_cnt_guard_ = create_reference(-1); + is_inited_ = true; loop(); } @@ -730,12 +916,15 @@ DcOptions ConnectionCreator::get_default_dc_options(bool is_test) { } void ConnectionCreator::loop() { + if (!is_inited_) { + return; + } if (!network_flag_) { return; } Timestamp timeout; - if (proxy_.type() == Proxy::Type::Mtproto) { + if (active_proxy_id_ != 0 && proxies_[active_proxy_id_].type() == Proxy::Type::Mtproto) { if (get_proxy_info_timestamp_.is_in_past()) { if (get_proxy_info_query_token_ == 0) { get_proxy_info_query_token_ = next_token(); @@ -749,12 +938,13 @@ void ConnectionCreator::loop() { } } - if (proxy_.type() != Proxy::Type::None) { + if (active_proxy_id_ != 0) { if (resolve_proxy_timestamp_.is_in_past()) { if (resolve_proxy_query_token_ == 0) { resolve_proxy_query_token_ = next_token(); + const Proxy &proxy = proxies_[active_proxy_id_]; send_closure( - get_host_by_name_actor_, &GetHostByNameActor::run, proxy_.server().str(), proxy_.port(), + get_host_by_name_actor_, &GetHostByNameActor::run, proxy.server().str(), proxy.port(), PromiseCreator::lambda([actor_id = create_reference(resolve_proxy_query_token_)](Result result) { send_closure(std::move(actor_id), &ConnectionCreator::on_proxy_resolved, std::move(result), false); })); diff --git a/td/telegram/net/ConnectionCreator.h b/td/telegram/net/ConnectionCreator.h index b5592b94a..ce0639318 100644 --- a/td/telegram/net/ConnectionCreator.h +++ b/td/telegram/net/ConnectionCreator.h @@ -47,40 +47,6 @@ namespace td { class Proxy { public: - tl_object_ptr as_td_api() const { - switch (type_) { - case Type::None: - return make_tl_object(); - case Type::Socks5: - return make_tl_object(server_, port_, user_, password_); - case Type::Mtproto: - return make_tl_object(server_, port_, secret_); - } - UNREACHABLE(); - return nullptr; - } - - static Proxy from_td_api(const tl_object_ptr &proxy) { - if (proxy == nullptr) { - return Proxy(); - } - - switch (proxy->get_id()) { - case td_api::proxyEmpty::ID: - return Proxy(); - case td_api::proxySocks5::ID: { - auto &socks5_proxy = static_cast(*proxy); - return Proxy::socks5(socks5_proxy.server_, socks5_proxy.port_, socks5_proxy.username_, socks5_proxy.password_); - } - case td_api::proxyMtproto::ID: { - auto &mtproto_proxy = static_cast(*proxy); - return Proxy::mtproto(mtproto_proxy.server_, mtproto_proxy.port_, mtproto_proxy.secret_); - } - } - UNREACHABLE(); - return Proxy(); - } - static Proxy socks5(string server, int32 port, string user, string password) { Proxy proxy; proxy.type_ = Type::Socks5; @@ -165,8 +131,13 @@ class ConnectionCreator : public NetQueryCallback { void set_net_stats_callback(std::shared_ptr common_callback, std::shared_ptr media_callback); - void set_proxy(Proxy proxy); - void get_proxy(Promise promise); + void add_proxy(string server, int32 port, bool enable, td_api::object_ptr proxy_type, + Promise> promise); + void enable_proxy(int32 proxy_id, Promise promise); + void disable_proxy(Promise promise); + void remove_proxy(int32 proxy_id, Promise promise); + void get_proxies(Promise> promise); + void ping_proxy(int32 proxy_id, Promise promise); private: ActorShared<> parent_; @@ -174,8 +145,12 @@ class ConnectionCreator : public NetQueryCallback { bool network_flag_ = false; uint32 network_generation_ = 0; bool online_flag_ = false; + bool is_inited_ = false; - Proxy proxy_; + std::map proxies_; + std::map proxy_last_used_date_; + int32 max_proxy_id_ = 0; + int32 active_proxy_id_ = 0; ActorOwn get_host_by_name_actor_; IPAddress proxy_ip_address_; Timestamp resolve_proxy_timestamp_; @@ -243,7 +218,13 @@ class ConnectionCreator : public NetQueryCallback { uint64 next_token() { return ++current_token_; } - void set_proxy_impl(Proxy proxy, bool from_db); + + void enable_proxy_impl(int32 proxy_id); + void disable_proxy_impl(); + void on_proxy_changed(bool from_db); + static string get_proxy_database_key(int32 proxy_id); + static string get_proxy_used_database_key(int32 proxy_id); + td_api::object_ptr get_proxy_object(int32 proxy_id) const; void start_up() override; void hangup_shared() override; diff --git a/td/telegram/net/Session.cpp b/td/telegram/net/Session.cpp index e45bd4a0c..35a53ed54 100644 --- a/td/telegram/net/Session.cpp +++ b/td/telegram/net/Session.cpp @@ -37,6 +37,7 @@ namespace td { namespace detail { + class GenAuthKeyActor : public Actor { public: GenAuthKeyActor(std::unique_ptr handshake, @@ -83,13 +84,16 @@ class GenAuthKeyActor : public Actor { handshake_promise_.set_value(std::move(handshake_)); return; } + auto raw_connection = r_raw_connection.move_as_ok(); + VLOG(dc) << "Receive raw connection " << raw_connection.get(); network_generation_ = raw_connection->extra_; child_ = create_actor_on_scheduler( "HandshakeActor", G()->get_slow_net_scheduler_id(), std::move(handshake_), std::move(raw_connection), std::move(context_), 10, std::move(connection_promise_), std::move(handshake_promise_)); } }; + } // namespace detail Session::Session(unique_ptr callback, std::shared_ptr shared_auth_data, bool is_main, @@ -178,7 +182,7 @@ void Session::connection_online_update(bool force) { return; } connection_online_flag_ = new_connection_online_flag; - LOG(INFO) << "Set connection_online " << connection_online_flag_; + VLOG(dc) << "Set connection_online " << connection_online_flag_; if (is_main_) { if (main_connection_.connection) { main_connection_.connection->set_online(connection_online_flag_); @@ -847,9 +851,10 @@ void Session::connection_open(ConnectionInfo *info, bool ask_info) { }); if (cached_connection_) { - LOG(INFO) << "Reuse cached connection"; + VLOG(dc) << "Reuse cached connection"; promise.set_value(std::move(cached_connection_)); } else { + VLOG(dc) << "Request new connection"; callback_->request_raw_connection(std::move(promise)); } @@ -857,7 +862,7 @@ void Session::connection_open(ConnectionInfo *info, bool ask_info) { } void Session::connection_add(std::unique_ptr raw_connection) { - LOG(INFO) << "Cache connection"; + VLOG(dc) << "Cache connection " << raw_connection.get(); cached_connection_ = std::move(raw_connection); cached_connection_timestamp_ = Time::now(); } @@ -875,10 +880,10 @@ void Session::connection_check_mode(ConnectionInfo *info) { void Session::connection_open_finish(ConnectionInfo *info, Result> r_raw_connection) { if (close_flag_ || info->state != ConnectionInfo::State::Connecting) { + VLOG(dc) << "Ignore raw connection while closing"; return; } current_info_ = info; - // Create new connection if (r_raw_connection.is_error()) { LOG(WARNING) << "Failed to open socket: " << r_raw_connection.error(); info->state = ConnectionInfo::State::Empty; @@ -887,6 +892,7 @@ void Session::connection_open_finish(ConnectionInfo *info, } auto raw_connection = r_raw_connection.move_as_ok(); + VLOG(dc) << "Receive raw connection " << raw_connection.get(); if (raw_connection->extra_ != network_generation_) { LOG(WARNING) << "Got RawConnection with old network_generation"; info->state = ConnectionInfo::State::Empty; @@ -897,10 +903,10 @@ void Session::connection_open_finish(ConnectionInfo *info, Mode expected_mode = raw_connection->get_transport_type().type == mtproto::TransportType::Http ? Mode::Http : Mode::Tcp; if (mode_ != expected_mode) { - LOG(INFO) << "Change mode " << mode_ << "--->" << expected_mode; + VLOG(dc) << "Change mode " << mode_ << "--->" << expected_mode; mode_ = expected_mode; if (info->connection_id == 1 && mode_ != Mode::Http) { - LOG(WARNING) << "Got tcp connection, for long poll connection"; + LOG(WARNING) << "Got tcp connection for long poll connection"; connection_add(std::move(raw_connection)); info->state = ConnectionInfo::State::Empty; yield(); diff --git a/tdactor/td/actor/impl/Scheduler.cpp b/tdactor/td/actor/impl/Scheduler.cpp index 479e419d6..1e41c9672 100644 --- a/tdactor/td/actor/impl/Scheduler.cpp +++ b/tdactor/td/actor/impl/Scheduler.cpp @@ -409,7 +409,7 @@ void Scheduler::set_actor_timeout_in(ActorInfo *actor_info, double timeout) { void Scheduler::set_actor_timeout_at(ActorInfo *actor_info, double timeout_at) { HeapNode *heap_node = actor_info->get_heap_node(); - VLOG(actor) << "set actor " << *actor_info << " " << tag("timeout", timeout_at) << timeout_at - Time::now_cached(); + VLOG(actor) << "Set actor " << *actor_info << " timeout in " << timeout_at - Time::now_cached(); if (heap_node->in_heap()) { timeout_queue_.fix(timeout_at, heap_node); } else {