Add --td-proxy for routing MTProto traffic through SOCKS5/HTTP/MTProto proxy

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.
main
Regela 4 weeks ago
parent 01a3679c0b
commit 45446e1752

@ -8731,6 +8731,29 @@ void Client::on_update_authorization_state() {
td::make_unique<TdOnOkCallback>()); td::make_unique<TdOnOkCallback>());
} }
if (parameters_->td_proxy_.type != TdProxyType::None) {
const auto &p = parameters_->td_proxy_;
object_ptr<td_api::ProxyType> type;
switch (p.type) {
case TdProxyType::Socks5:
type = make_object<td_api::proxyTypeSocks5>(p.username, p.password);
break;
case TdProxyType::Http:
type = make_object<td_api::proxyTypeHttp>(p.username, p.password, p.http_only);
break;
case TdProxyType::Mtproto:
type = make_object<td_api::proxyTypeMtproto>(p.secret);
break;
default:
break;
}
if (type != nullptr) {
auto proxy = make_object<td_api::proxy>(p.server, p.port, std::move(type));
send_request(make_object<td_api::addProxy>(std::move(proxy), true, "telegram-bot-api --td-proxy"),
td::make_unique<TdOnOkCallback>());
}
}
auto request = make_object<td_api::setTdlibParameters>(); auto request = make_object<td_api::setTdlibParameters>();
request->use_test_dc_ = is_test_dc_; request->use_test_dc_ = is_test_dc_;
request->database_directory_ = dir_; request->database_directory_ = dir_;

@ -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 { struct ClientParameters {
td::string working_directory_; td::string working_directory_;
bool allow_colon_in_filenames_ = true; bool allow_colon_in_filenames_ = true;
@ -117,6 +129,8 @@ struct ClientParameters {
td::int32 default_max_webhook_connections_ = 0; td::int32 default_max_webhook_connections_ = 0;
td::IPAddress webhook_proxy_ip_address_; td::IPAddress webhook_proxy_ip_address_;
TdProxyParameters td_proxy_;
double start_time_ = 0; double start_time_ = 0;
td::ActorId<td::GetHostByNameActor> get_host_by_name_actor_id_; td::ActorId<td::GetHostByNameActor> get_host_by_name_actor_id_;

@ -298,6 +298,64 @@ int main(int argc, char *argv[]) {
} }
return parameters->webhook_proxy_ip_address_.init_host_port(address.str()); 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<td::int32>(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([&] { options.add_check([&] {
if (parameters->api_id_ <= 0 || parameters->api_hash_.empty()) { 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"); return td::Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");

Loading…
Cancel
Save