From 45446e175218efb68536d90689b35ace5b716881 Mon Sep 17 00:00:00 2001 From: Regela Date: Fri, 22 May 2026 21:29:05 +0300 Subject: [PATCH] Add --td-proxy for routing MTProto traffic through SOCKS5/HTTP/MTProto proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The upstream --proxy option only affects outgoing webhook requests, leaving no way to route connections to Telegram MTProto datacenters through a proxy. This is a blocker in restricted network environments where direct egress to 149.154.x.x is unavailable but a SOCKS5/HTTP CONNECT proxy is reachable. Add a new --td-proxy=URL option that parses one of: - socks5://[user:pass@]host:port - socks5h://[user:pass@]host:port (same as socks5 — TDLib always resolves remotely) - http://[user:pass@]host:port - mtproto://secret@host:port The parsed parameters are passed to TDLib via td_api::addProxy with enable=true, sent before setTdlibParameters so the very first network attempt is already routed through the proxy. Verified end-to-end with gost: both socks5 and http modes route MTProto to DC IPs (149.154.x.x:443) via the proxy with no direct egress. --- telegram-bot-api/Client.cpp | 23 +++++++++++ telegram-bot-api/ClientParameters.h | 14 +++++++ telegram-bot-api/telegram-bot-api.cpp | 58 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/telegram-bot-api/Client.cpp b/telegram-bot-api/Client.cpp index 66828de..0697e6c 100644 --- a/telegram-bot-api/Client.cpp +++ b/telegram-bot-api/Client.cpp @@ -8731,6 +8731,29 @@ void Client::on_update_authorization_state() { td::make_unique()); } + if (parameters_->td_proxy_.type != TdProxyType::None) { + const auto &p = parameters_->td_proxy_; + object_ptr type; + switch (p.type) { + case TdProxyType::Socks5: + type = make_object(p.username, p.password); + break; + case TdProxyType::Http: + type = make_object(p.username, p.password, p.http_only); + break; + case TdProxyType::Mtproto: + type = make_object(p.secret); + break; + default: + break; + } + if (type != nullptr) { + auto proxy = make_object(p.server, p.port, std::move(type)); + send_request(make_object(std::move(proxy), true, "telegram-bot-api --td-proxy"), + td::make_unique()); + } + } + auto request = make_object(); request->use_test_dc_ = is_test_dc_; request->database_directory_ = dir_; diff --git a/telegram-bot-api/ClientParameters.h b/telegram-bot-api/ClientParameters.h index f6d252a..1a910e2 100644 --- a/telegram-bot-api/ClientParameters.h +++ b/telegram-bot-api/ClientParameters.h @@ -103,6 +103,18 @@ struct SharedData { } }; +enum class TdProxyType { None, Socks5, Http, Mtproto }; + +struct TdProxyParameters { + TdProxyType type = TdProxyType::None; + td::string server; + td::int32 port = 0; + td::string username; + td::string password; + td::string secret; // for Mtproto + bool http_only = false; // for Http: route only HTTP, not TCP-tunneled MTProto +}; + struct ClientParameters { td::string working_directory_; bool allow_colon_in_filenames_ = true; @@ -117,6 +129,8 @@ struct ClientParameters { td::int32 default_max_webhook_connections_ = 0; td::IPAddress webhook_proxy_ip_address_; + TdProxyParameters td_proxy_; + double start_time_ = 0; td::ActorId get_host_by_name_actor_id_; diff --git a/telegram-bot-api/telegram-bot-api.cpp b/telegram-bot-api/telegram-bot-api.cpp index 41b0fc8..d886e82 100644 --- a/telegram-bot-api/telegram-bot-api.cpp +++ b/telegram-bot-api/telegram-bot-api.cpp @@ -298,6 +298,64 @@ int main(int argc, char *argv[]) { } return parameters->webhook_proxy_ip_address_.init_host_port(address.str()); }); + options.add_checked_option( + '\0', "td-proxy", + "proxy for connections to Telegram MTProto servers; supported schemes: " + "socks5://[user:pass@]host:port, socks5h://[user:pass@]host:port (same as socks5 — DNS is always remote), " + "http://[user:pass@]host:port (CONNECT-capable), mtproto://secret@host:port (secret in hex)", + [&](td::Slice url) { + TdProxyParameters &p = parameters->td_proxy_; + if (td::begins_with(url, "socks5h://")) { + p.type = TdProxyType::Socks5; + url.remove_prefix(10); + } else if (td::begins_with(url, "socks5://")) { + p.type = TdProxyType::Socks5; + url.remove_prefix(9); + } else if (td::begins_with(url, "http://")) { + p.type = TdProxyType::Http; + url.remove_prefix(7); + } else if (td::begins_with(url, "mtproto://")) { + p.type = TdProxyType::Mtproto; + url.remove_prefix(10); + } else { + return td::Status::Error("--td-proxy: unsupported scheme (use socks5://, socks5h://, http:// or mtproto://)"); + } + auto at_pos = url.rfind('@'); + td::Slice userinfo; + td::Slice hostport = url; + if (at_pos != td::Slice::npos) { + userinfo = url.substr(0, at_pos); + hostport = url.substr(at_pos + 1); + } + auto colon_pos = hostport.rfind(':'); + if (colon_pos == td::Slice::npos) { + return td::Status::Error("--td-proxy: missing port (host:port)"); + } + p.server = hostport.substr(0, colon_pos).str(); + TRY_RESULT(port, td::to_integer_safe(hostport.substr(colon_pos + 1))); + if (port <= 0 || port > 65535) { + return td::Status::Error("--td-proxy: invalid port"); + } + p.port = port; + if (p.server.empty()) { + return td::Status::Error("--td-proxy: empty host"); + } + if (p.type == TdProxyType::Mtproto) { + if (userinfo.empty()) { + return td::Status::Error("--td-proxy: mtproto proxy requires a secret (mtproto://SECRET@host:port)"); + } + p.secret = userinfo.str(); + } else if (!userinfo.empty()) { + auto user_colon = userinfo.find(':'); + if (user_colon == td::Slice::npos) { + p.username = userinfo.str(); + } else { + p.username = userinfo.substr(0, user_colon).str(); + p.password = userinfo.substr(user_colon + 1).str(); + } + } + return td::Status::OK(); + }); options.add_check([&] { if (parameters->api_id_ <= 0 || parameters->api_hash_.empty()) { return td::Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");