ZaStoGram_desktop/Telegram/SourceFiles/mtproto/session/private/auth.cpp
loop-uh 29f3ac7109
All checks were successful
Desktop source guards / guards (push) Successful in 6s
Не убивать туннель через секунду и не терять медиа-ключ DC1 в WSS
По логу dev-26 (25.09, 18:03) файлы DC1 всё ещё не грузились:

- Первый бюджет подключения 1 с, а туннелю нужно ~0,7 с на TCP+TLS+upgrade
  и ~1,1 с до первого ответа: почти каждое подключение туннеля убивалось
  до ответа и засчитывалось как «туннель молчит», и после трёх таких
  туннель отключался. Сессия, которая идёт через туннель, теперь ждёт
  подключения не меньше 4 с, а тишиной считается только сокет, который
  после upgrade молчал 4 с и больше.
- Медиа-сессия DC1 держала обычный временный ключ DC, а туннель с -1 в
  заголовке (как и релей kws1-1) приводит в медиа-кластер, который этого
  ключа не знает: -404 на каждом соединении, ключ уничтожался и создавался
  заново, и замерзающее соединение тратило свои ~16 КБ на создание ключа,
  а не на файл. dropMismatchedTemporaryKey, сделанный для MTProxy и WEB,
  теперь работает и для WSS.
2026-09-25 18:05:48 +03:00

405 lines
13 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "mtproto/session/private/session_private.h"
#include "core/version.h"
#include "mtproto/details/mtproto_dcenter.h"
#include "mtproto/instance/mtp_instance.h"
#include "mtproto/protocol/mtproto_dump_to_text.h"
#include "mtproto/details/mtproto_rsa_public_key.h"
#include "mtproto/proxy/diagnostics.h"
#include "mtproto/proxy/transport_policy.h"
#include "mtproto/session/session.h"
#include "mtproto/protocol/mtproto_response.h"
#include "mtproto/config/mtproto_dc_options.h"
#include "mtproto/transport/connection_abstract.h"
#include "base/options.h"
#include "base/random.h"
#include "base/qthelp_url.h"
#include "base/openssl_help.h"
#include "base/unixtime.h"
#include "base/platform/base_platform_info.h"
#include <ksandbox.h>
#include <zlib.h>
namespace MTP {
namespace details {
namespace {
constexpr auto kTemporaryExpiresIn = TimeId(86400);
constexpr auto kBindKeyAdditionalExpiresTimeout = TimeId(30);
constexpr auto kKeyOldEnoughForDestroy = 60 * crl::time(1000);
} // namespace
SessionMessageHandler::HandleResult SessionMessageHandler::handleBindResponse(
mtpMsgId requestMsgId,
const mtpBuffer &response) {
if (!_owner->_authState.keyCreator
|| !_owner->_authState.bindMsgId
|| _owner->_authState.bindMsgId != requestMsgId) {
return HandleResult::Ignored;
}
_owner->_authState.bindMsgId = 0;
const auto result = _owner->_authState.keyCreator->handleBindResponse(
response);
switch (result) {
case DcKeyBindState::Success:
if (!_owner->_sessionState.data->releaseKeyCreationOnDone(
_owner->_sessionState.encryptionKey,
base::take(_owner->_authState.keyCreator)->bindPersistentKey())) {
return HandleResult::DestroyTemporaryKey;
}
_owner->logMtprotoEvent(
ProxyDiagnosticsPhase::MtpKeyReady,
ProxyDiagnosticsSeverity::Info,
u"temporary key bound (id %1)"_q.arg(_owner->_sessionState.keyId));
_owner->_sessionState.data->queueNeedToResumeAndSend();
return HandleResult::Success;
case DcKeyBindState::DefinitelyDestroyed:
if (_owner->destroyOldEnoughPersistentKey()) {
_owner->logMtprotoEvent(
ProxyDiagnosticsPhase::MtpBindFailed,
ProxyDiagnosticsSeverity::Warning,
u"bind failed, persistent key destroyed on server"_q);
return HandleResult::DestroyTemporaryKey;
}
[[fallthrough]];
case DcKeyBindState::Failed:
_owner->logMtprotoEvent(
ProxyDiagnosticsPhase::MtpBindFailed,
ProxyDiagnosticsSeverity::Warning,
u"temporary key bind failed"_q);
_owner->_sessionState.data->queueNeedToResumeAndSend();
return HandleResult::Success;
}
Unexpected("Result of SessionBoundKeyCreator::handleBindResponse.");
}
void SessionPrivate::checkAuthKey() {
logMtprotoEvent(
ProxyDiagnosticsPhase::MtpTransportReady,
ProxyDiagnosticsSeverity::Info,
u"transport ready via %1, handshake %2ms, key id %3"_q
.arg(_transport.connectionTag())
.arg(_transport.connectionPingTime())
.arg(_sessionState.keyId));
if (_sessionState.keyId) {
authKeyChecked();
} else if (_delegate->isKeysDestroyer()) {
applyAuthKey(_sessionState.data->getPersistentKey());
} else {
applyAuthKey(_sessionState.data->getTemporaryKey(
TemporaryKeyTypeByDcType(_currentDcType)));
}
}
void SessionPrivate::updateAuthKey() {
if (_delegate->isKeysDestroyer()
|| _authState.keyCreator
|| !_transport.connection()) {
return;
}
DEBUG_LOG(("AuthKey Info: Connection updating key from Session, dc %1"
).arg(_shiftedDcId));
applyAuthKey(_sessionState.data->getTemporaryKey(
TemporaryKeyTypeByDcType(_currentDcType)));
}
void SessionPrivate::setCurrentKeyId(uint64 newKeyId) {
if (_sessionState.keyId == newKeyId) {
return;
}
_sessionState.keyId = newKeyId;
DEBUG_LOG(("MTP Info: auth key id set to id %1").arg(newKeyId));
changeSessionId();
}
void SessionPrivate::applyAuthKey(AuthKeyPtr &&encryptionKey) {
_sessionState.encryptionKey = std::move(encryptionKey);
const auto newKeyId = _sessionState.encryptionKey ? _sessionState.encryptionKey->keyId() : 0;
if (_sessionState.keyId) {
if (_sessionState.keyId == newKeyId) {
return;
}
setCurrentKeyId(0);
DEBUG_LOG(("MTP Info: auth_key id for dc %1 changed, restarting..."
).arg(_shiftedDcId));
if (_transport.connection()) {
restart();
}
return;
}
if (!_transport.connection()) {
return;
}
setCurrentKeyId(newKeyId);
DEBUG_LOG(("AuthKey Info: Connection update key from Session, "
"dc %1 result: %2"
).arg(_shiftedDcId
).arg(Logs::mb(&_sessionState.keyId, sizeof(_sessionState.keyId)).str()));
if (_sessionState.keyId) {
return authKeyChecked();
}
if (_delegate->isKeysDestroyer()) {
// We are here to destroy an old key, so we're done.
LOG(("MTP Error: No key %1 in updateAuthKey() for destroying."
).arg(_shiftedDcId));
_delegate->keyWasPossiblyDestroyed(_shiftedDcId);
} else if (noMediaKeyWithExistingRegularKey()) {
DEBUG_LOG(("AuthKey Info: No key in updateAuthKey() for media, "
"but someone has created regular, trying to acquire."));
const auto dcType = tryAcquireKeyCreation();
if (_authState.keyCreator && dcType != _currentDcType) {
DEBUG_LOG(("AuthKey Info: "
"Dc type changed for creation, restarting."));
restart();
return;
}
}
if (_authState.keyCreator) {
DEBUG_LOG(("AuthKey Info: No key in updateAuthKey(), creating."));
logMtprotoEvent(
ProxyDiagnosticsPhase::MtpKeyCreating,
ProxyDiagnosticsSeverity::Info,
u"no auth key, starting key creation"_q);
_authState.keyCreator->start(
BareDcId(_shiftedDcId),
getProtocolDcId(),
not_null<AbstractConnection*>{ _transport.connection() },
&_delegate->dcOptions());
} else {
DEBUG_LOG(("AuthKey Info: No key in updateAuthKey(), "
"but someone is creating already, waiting."));
logMtprotoEvent(
ProxyDiagnosticsPhase::MtpKeyCreating,
ProxyDiagnosticsSeverity::Info,
u"no auth key, waiting for creation by another session"_q);
}
}
bool SessionPrivate::noMediaKeyWithExistingRegularKey() const {
return (TemporaryKeyTypeByDcType(_currentDcType)
== TemporaryKeyType::MediaCluster)
&& _sessionState.data->getTemporaryKey(TemporaryKeyType::Regular);
}
void SessionPrivate::dropMismatchedTemporaryKey() {
// A media session creates the regular temporary key itself when it is
// the first session of its DC (see Dcenter::acquireKeyCreation), and it
// kept that key after the next connect switched it to the media
// cluster. Through MTProxy and WEB proxies the DC id in the transport
// header picks the backend, -N is the media cluster, and it does not
// know the regular key: every packet came back as -404, the regular key
// of the whole DC was destroyed, recreated by the same session through
// the regular DC and the loop started over. Give the media session the
// key of its own cluster (created on this connection if missing).
const auto proxy = _sessionState.options
? _sessionState.options->proxy.type
: ProxyData::Type::None;
// WSS is the same: the kwsN-1 relay and the Cloudflare tunnel (with -N
// in the obfuscated header) both land on the media cluster. Desktop log
// 25.09: DC1 media through the tunnel got -404 on every connection and
// spent the ~16 KB each tunnel connection gets on creating keys again.
const auto wss = (proxy == ProxyData::Type::None)
&& _sessionState.options
&& (_sessionState.options->stealth.transport
== ProxyTransport::Wss);
if (!_sessionState.encryptionKey
|| _delegate->isKeysDestroyer()
|| (proxy != ProxyData::Type::Mtproto
&& proxy != ProxyData::Type::Web
&& !wss)
|| (TemporaryKeyTypeByDcType(_currentDcType)
!= TemporaryKeyType::MediaCluster)) {
return;
}
const auto regular = _sessionState.data->getTemporaryKey(
TemporaryKeyType::Regular);
if (!regular || regular != _sessionState.encryptionKey) {
return;
}
logMtprotoEvent(
ProxyDiagnosticsPhase::MtpKeyCreating,
ProxyDiagnosticsSeverity::Info,
u"media session left the regular temporary key (id %1) "
"for the media cluster key"_q.arg(regular->keyId()));
applyAuthKey(nullptr);
}
bool SessionPrivate::destroyOldEnoughPersistentKey() {
Expects(_authState.keyCreator != nullptr);
const auto key = _authState.keyCreator->bindPersistentKey();
Assert(key != nullptr);
const auto created = key->creationTime();
if (created > 0 && crl::now() - created < kKeyOldEnoughForDestroy) {
return false;
}
const auto instance = _instance;
const auto delegate = _delegate;
const auto shiftedDcId = _shiftedDcId;
const auto keyId = key->keyId();
InvokeQueued(instance, [=] {
delegate->keyDestroyedOnServer(shiftedDcId, keyId);
});
return true;
}
DcType SessionPrivate::tryAcquireKeyCreation() {
if (_authState.keyCreator) {
return _currentDcType;
} else if (_delegate->isKeysDestroyer()) {
return _realDcType;
}
const auto acquired = _sessionState.data->acquireKeyCreation(_realDcType);
if (acquired == CreatingKeyType::None) {
return _realDcType;
}
using Result = DcKeyResult;
using Error = DcKeyError;
auto delegate = SessionAuthKeyDelegate();
delegate.unboundReady = [=](base::expected<Result, Error> result) {
if (!result) {
releaseKeyCreationOnFail();
if (result.error() == Error::UnknownPublicKey) {
if (_realDcType == DcType::Cdn) {
LOG(("Warning: CDN public RSA key not found"));
_transport.requestCDNConfig();
return;
}
LOG(("AuthKey Error: could not choose public RSA key"));
}
restart();
return;
}
DEBUG_LOG(("AuthKey Info: unbound key creation succeed, "
"ids: (%1, %2) server salts: (%3, %4)"
).arg(result->temporaryKey
? result->temporaryKey->keyId()
: 0
).arg(result->persistentKey
? result->persistentKey->keyId()
: 0
).arg(result->temporaryServerSalt
).arg(result->persistentServerSalt));
_sessionState.sessionSalt = result->temporaryServerSalt;
result->temporaryKey->setExpiresAt(base::unixtime::now()
+ kTemporaryExpiresIn
+ kBindKeyAdditionalExpiresTimeout);
if (_realDcType != DcType::Cdn) {
auto key = result->persistentKey
? std::move(result->persistentKey)
: _sessionState.data->getPersistentKey();
if (!key) {
releaseKeyCreationOnFail();
restart();
return;
}
_authState.keyCreator->bind(std::move(key));
}
applyAuthKey(std::move(result->temporaryKey));
if (_realDcType == DcType::Cdn) {
_authState.keyCreator = nullptr;
if (!_sessionState.data->releaseCdnKeyCreationOnDone(_sessionState.encryptionKey)) {
restart();
} else {
_sessionState.data->queueNeedToResumeAndSend();
}
}
};
delegate.sentSome = [=](uint64 size) {
onSentSome(size);
};
delegate.receivedSome = [=] {
onReceivedSome();
};
auto request = DcKeyRequest();
request.persistentNeeded = (acquired == CreatingKeyType::Persistent);
request.temporaryExpiresIn = kTemporaryExpiresIn;
_authState.keyCreator = _authKeyFactory->create(
request,
std::move(delegate));
const auto forceUseRegular = (_realDcType == DcType::MediaCluster)
&& (acquired != CreatingKeyType::TemporaryMediaCluster);
return forceUseRegular ? DcType::Regular : _realDcType;
}
void SessionPrivate::authKeyChecked() {
connect(_transport.connection(), &AbstractConnection::receivedData, [=] {
handleReceived();
});
logMtprotoEvent(
ProxyDiagnosticsPhase::MtpKeyReady,
ProxyDiagnosticsSeverity::Info,
u"auth key ready (id %1), %2"_q
.arg(_sessionState.keyId)
.arg(_sessionState.sessionSalt
? u"session connected"_q
: u"requesting server salt"_q));
if (_sessionState.sessionSalt && setState(ConnectedState)) {
_messageHandler.resendAll();
} // else receive salt in bad_server_salt first, then try to send all the requests
_requestState.pingIdToSend = base::RandomValue<uint64>(); // get server_salt
_sessionState.data->queueNeedToResumeAndSend();
}
void SessionPrivate::destroyTemporaryKey() {
if (_delegate->isKeysDestroyer()) {
LOG(("MTP Info: -404 error received in destroyer %1, assuming key was destroyed.").arg(_shiftedDcId));
logMtprotoEvent(
ProxyDiagnosticsPhase::MtpKeyDestroyed,
ProxyDiagnosticsSeverity::Info,
u"key destroyer confirmed key gone"_q);
_delegate->keyWasPossiblyDestroyed(_shiftedDcId);
return;
}
LOG(("MTP Info: -404 error received in %1 with temporary key, assuming it was destroyed.").arg(_shiftedDcId));
logMtprotoEvent(
ProxyDiagnosticsPhase::MtpKeyDestroyed,
ProxyDiagnosticsSeverity::Warning,
u"temporary key (id %1) assumed destroyed by server, recreating"_q
.arg(_sessionState.keyId));
releaseKeyCreationOnFail();
if (_sessionState.encryptionKey) {
_sessionState.data->destroyTemporaryKey(_sessionState.encryptionKey->keyId());
}
applyAuthKey(nullptr);
restart();
}
void SessionPrivate::clearUnboundKeyCreator() {
if (_authState.keyCreator) {
_authState.keyCreator->stop();
}
}
void SessionPrivate::releaseKeyCreationOnFail() {
if (!_authState.keyCreator) {
return;
}
_authState.keyCreator = nullptr;
_sessionState.data->releaseKeyCreationOnFail();
}
} // namespace details
} // namespace MTP