All checks were successful
Desktop source guards / guards (push) Successful in 12s
ТСПУ замораживает каждое соединение с Cloudflare примерно после 16 КБ входящих: на ПК (лог 25.09) ни одна сессия туннеля к DC1 не получила больше 13 КБ, а часть файла 128 КБ целиком не проходила ни разу (send_count=51), поэтому файлы в «Избранном» стояли на 0. - Для DC, чей медийный релей подавлен и ушёл в туннель, часть 128 КБ собирается из 16 кусков по 8 КБ; для CDN части не делятся. - До 8 сессий сразу, в каждой один запрос: скорость даёт параллельность. - Файловое соединение туннеля переоткрывается на границе пакета после 4 КБ входящих, до заморозки; такие сокеты пишутся одной сводной строкой wss_tunnel_rotated, строки переподключения для них не пишутся. - Туннель подавляется только если после upgrade не пришло ничего: раньше три заморозки отправляли DC1 в прямой TCP, который сеть режет целиком, и файлы не грузились совсем по две минуты.
949 lines
26 KiB
C++
949 lines
26 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/transport/connection_tcp.h"
|
|
|
|
#include "base/bytes.h"
|
|
#include "base/invoke_queued.h"
|
|
#include "base/openssl_help.h"
|
|
#include "base/qthelp_url.h"
|
|
#include "base/random.h"
|
|
#include "mtproto/protocol/mtproto_binary.h"
|
|
#include "mtproto/proxy/diagnostics.h"
|
|
#include "mtproto/proxy/proxy_endpoint_context.h"
|
|
#include "mtproto/proxy/socket_factory.h"
|
|
#include "mtproto/proxy/transport_policy.h"
|
|
#include "mtproto/runtime/runtime_environment.h"
|
|
#include "mtproto/transport/details/mtproto_abstract_socket.h"
|
|
|
|
#include <QtCore/QtEndian>
|
|
|
|
namespace MTP {
|
|
namespace details {
|
|
namespace {
|
|
|
|
constexpr auto kPacketSizeMax = int(0x01000000 * sizeof(mtpPrime));
|
|
constexpr auto kFullConnectionTimeout = 8 * crl::time(1000);
|
|
// A cold WEB carrier is allowed to spend up to 45 seconds navigating and
|
|
// negotiating its browser bridge. Keep the outer MTP connection alive long
|
|
// enough for that contract plus the first Telegram round trip; otherwise the
|
|
// session tears down a healthy logical stream while the carrier is still
|
|
// legitimately connecting.
|
|
constexpr auto kWebProxyFullConnectionTimeout = 50 * crl::time(1000);
|
|
constexpr auto kSmallBufferSize = 256 * 1024;
|
|
constexpr auto kMinPacketBuffer = 256;
|
|
constexpr auto kConnectionStartPrefixSize = 64;
|
|
|
|
} // namespace
|
|
|
|
class TcpConnection::Protocol {
|
|
public:
|
|
static std::unique_ptr<Protocol> Create(bytes::const_span secret);
|
|
|
|
virtual uint32 id() const = 0;
|
|
virtual bool supportsArbitraryLength() const = 0;
|
|
|
|
virtual void prepareKey(bytes::span key, bytes::const_span source) = 0;
|
|
virtual bytes::span finalizePacket(mtpBuffer &buffer) = 0;
|
|
|
|
static constexpr auto kUnknownSize = -1;
|
|
static constexpr auto kInvalidSize = -2;
|
|
virtual int readPacketLength(bytes::const_span bytes) const = 0;
|
|
virtual bytes::const_span readPacket(bytes::const_span bytes) const = 0;
|
|
|
|
virtual QString debugPostfix() const = 0;
|
|
|
|
virtual ~Protocol() = default;
|
|
|
|
private:
|
|
class Version0;
|
|
class Version1;
|
|
class VersionD;
|
|
|
|
};
|
|
|
|
class TcpConnection::Protocol::Version0 : public Protocol {
|
|
public:
|
|
uint32 id() const override;
|
|
bool supportsArbitraryLength() const override;
|
|
|
|
void prepareKey(bytes::span key, bytes::const_span source) override;
|
|
bytes::span finalizePacket(mtpBuffer &buffer) override;
|
|
|
|
int readPacketLength(bytes::const_span bytes) const override;
|
|
bytes::const_span readPacket(bytes::const_span bytes) const override;
|
|
|
|
QString debugPostfix() const override;
|
|
|
|
};
|
|
|
|
uint32 TcpConnection::Protocol::Version0::id() const {
|
|
return 0xEFEFEFEFU;
|
|
}
|
|
|
|
bool TcpConnection::Protocol::Version0::supportsArbitraryLength() const {
|
|
return false;
|
|
}
|
|
|
|
void TcpConnection::Protocol::Version0::prepareKey(
|
|
bytes::span key,
|
|
bytes::const_span source) {
|
|
bytes::copy(key, source);
|
|
}
|
|
|
|
bytes::span TcpConnection::Protocol::Version0::finalizePacket(
|
|
mtpBuffer &buffer) {
|
|
Expects(buffer.size() > 2 && buffer.size() < 0x1000003U);
|
|
|
|
const auto intsSize = uint32(buffer.size() - 2);
|
|
const auto bytesSize = intsSize * sizeof(mtpPrime);
|
|
auto data = bytes::make_span(buffer);
|
|
const auto added = [&] {
|
|
if (intsSize < 0x7F) {
|
|
data[7] = bytes::type(uchar(intsSize));
|
|
return 1;
|
|
}
|
|
data[4] = bytes::type(uchar(0x7F));
|
|
data[5] = bytes::type(uchar(intsSize & 0xFF));
|
|
data[6] = bytes::type(uchar((intsSize >> 8) & 0xFF));
|
|
data[7] = bytes::type(uchar((intsSize >> 16) & 0xFF));
|
|
return 4;
|
|
}();
|
|
return bytes::make_span(buffer).subspan(8 - added, added + bytesSize);
|
|
}
|
|
|
|
int TcpConnection::Protocol::Version0::readPacketLength(
|
|
bytes::const_span bytes) const {
|
|
if (bytes.empty()) {
|
|
return kUnknownSize;
|
|
}
|
|
|
|
const auto first = static_cast<char>(bytes[0]);
|
|
if (first == 0x7F) {
|
|
if (bytes.size() < 4) {
|
|
return kUnknownSize;
|
|
}
|
|
const auto ints = static_cast<uint32>(bytes[1])
|
|
| (static_cast<uint32>(bytes[2]) << 8)
|
|
| (static_cast<uint32>(bytes[3]) << 16);
|
|
return (ints >= 0x7F) ? (int(ints << 2) + 4) : kInvalidSize;
|
|
} else if (first > 0 && first < 0x7F) {
|
|
const auto ints = uint32(first);
|
|
return int(ints << 2) + 1;
|
|
}
|
|
return kInvalidSize;
|
|
}
|
|
|
|
bytes::const_span TcpConnection::Protocol::Version0::readPacket(
|
|
bytes::const_span bytes) const {
|
|
const auto size = readPacketLength(bytes);
|
|
Assert(size != kUnknownSize
|
|
&& size != kInvalidSize
|
|
&& size <= bytes.size());
|
|
const auto sizeLength = (static_cast<char>(bytes[0]) == 0x7F) ? 4 : 1;
|
|
return bytes.subspan(sizeLength, size - sizeLength);
|
|
}
|
|
|
|
QString TcpConnection::Protocol::Version0::debugPostfix() const {
|
|
return QString();
|
|
}
|
|
|
|
class TcpConnection::Protocol::Version1 : public Version0 {
|
|
public:
|
|
explicit Version1(bytes::vector &&secret);
|
|
|
|
void prepareKey(bytes::span key, bytes::const_span source) override;
|
|
|
|
QString debugPostfix() const override;
|
|
|
|
private:
|
|
bytes::vector _secret;
|
|
|
|
};
|
|
|
|
TcpConnection::Protocol::Version1::Version1(bytes::vector &&secret)
|
|
: _secret(std::move(secret)) {
|
|
}
|
|
|
|
void TcpConnection::Protocol::Version1::prepareKey(
|
|
bytes::span key,
|
|
bytes::const_span source) {
|
|
const auto payload = bytes::concatenate(source, _secret);
|
|
bytes::copy(key, openssl::Sha256(payload));
|
|
}
|
|
|
|
QString TcpConnection::Protocol::Version1::debugPostfix() const {
|
|
return u"_obf"_q;
|
|
}
|
|
|
|
class TcpConnection::Protocol::VersionD : public Version1 {
|
|
public:
|
|
using Version1::Version1;
|
|
|
|
uint32 id() const override;
|
|
bool supportsArbitraryLength() const override;
|
|
|
|
bytes::span finalizePacket(mtpBuffer &buffer) override;
|
|
|
|
int readPacketLength(bytes::const_span bytes) const override;
|
|
bytes::const_span readPacket(bytes::const_span bytes) const override;
|
|
|
|
QString debugPostfix() const override;
|
|
|
|
};
|
|
|
|
uint32 TcpConnection::Protocol::VersionD::id() const {
|
|
return 0xDDDDDDDDU;
|
|
}
|
|
|
|
bool TcpConnection::Protocol::VersionD::supportsArbitraryLength() const {
|
|
return true;
|
|
}
|
|
|
|
bytes::span TcpConnection::Protocol::VersionD::finalizePacket(
|
|
mtpBuffer &buffer) {
|
|
Expects(buffer.size() > 2 && buffer.size() < 0x1000003U);
|
|
|
|
const auto intsSize = uint32(buffer.size() - 2);
|
|
const auto padding = base::RandomValue<uint32>() & 0x0F;
|
|
const auto bytesSize = intsSize * sizeof(mtpPrime) + padding;
|
|
buffer[1] = bytesSize;
|
|
for (auto added = 0; added < padding; added += 4) {
|
|
buffer.push_back(base::RandomValue<mtpPrime>());
|
|
}
|
|
|
|
return bytes::make_span(buffer).subspan(4, 4 + bytesSize);
|
|
}
|
|
|
|
int TcpConnection::Protocol::VersionD::readPacketLength(
|
|
bytes::const_span bytes) const {
|
|
if (bytes.size() < 4) {
|
|
return kUnknownSize;
|
|
}
|
|
const auto value = binary::Read<uint32>(bytes) + 4;
|
|
return (value >= 8 && value < kPacketSizeMax)
|
|
? int(value)
|
|
: kInvalidSize;
|
|
}
|
|
|
|
bytes::const_span TcpConnection::Protocol::VersionD::readPacket(
|
|
bytes::const_span bytes) const {
|
|
const auto size = readPacketLength(bytes);
|
|
Assert(size != kUnknownSize
|
|
&& size != kInvalidSize
|
|
&& size <= bytes.size());
|
|
const auto sizeLength = 4;
|
|
return bytes.subspan(sizeLength, size - sizeLength);
|
|
}
|
|
|
|
QString TcpConnection::Protocol::VersionD::debugPostfix() const {
|
|
return u"_dd"_q;
|
|
}
|
|
|
|
auto TcpConnection::Protocol::Create(bytes::const_span secret)
|
|
-> std::unique_ptr<Protocol> {
|
|
// See also DcOptions::ValidateSecret.
|
|
if ((secret.size() >= 21 && secret[0] == bytes::type(0xEE))
|
|
|| (secret.size() == 17 && secret[0] == bytes::type(0xDD))) {
|
|
return std::make_unique<VersionD>(
|
|
bytes::make_vector(secret.subspan(1, 16)));
|
|
} else if (secret.size() == 16) {
|
|
return std::make_unique<Version1>(bytes::make_vector(secret));
|
|
} else if (secret.empty()) {
|
|
return std::make_unique<Version0>();
|
|
}
|
|
Unexpected("Secret bytes in TcpConnection::Protocol::Create.");
|
|
}
|
|
|
|
TcpConnection::TcpConnection(
|
|
not_null<RuntimeEnvironment*> runtime,
|
|
QThread *thread,
|
|
const ProxyData &proxy,
|
|
const ProxyStealthOptions &stealth)
|
|
: AbstractConnection(runtime, thread, proxy)
|
|
, _stealth(stealth)
|
|
, _checkNonce(base::RandomValue<MTPint128>()) {
|
|
}
|
|
|
|
ConnectionPointer TcpConnection::clone(const ProxyData &proxy) {
|
|
return ConnectionPointer::New<TcpConnection>(
|
|
_runtime,
|
|
thread(),
|
|
proxy,
|
|
_stealth);
|
|
}
|
|
|
|
void TcpConnection::ensureAvailableInBuffer(int amount) {
|
|
auto &buffer = _usingLargeBuffer ? _largeBuffer : _smallBuffer;
|
|
const auto full = bytes::make_span(buffer).subspan(
|
|
_offsetBytes);
|
|
if (full.size() >= amount) {
|
|
return;
|
|
}
|
|
const auto read = full.subspan(0, _readBytes);
|
|
if (amount <= _smallBuffer.size()) {
|
|
if (_usingLargeBuffer) {
|
|
bytes::copy(_smallBuffer, read);
|
|
_usingLargeBuffer = false;
|
|
_largeBuffer.clear();
|
|
} else {
|
|
bytes::move(_smallBuffer, read);
|
|
}
|
|
} else if (amount <= _largeBuffer.size()) {
|
|
Assert(_usingLargeBuffer);
|
|
bytes::move(_largeBuffer, read);
|
|
} else {
|
|
auto enough = bytes::vector(amount);
|
|
bytes::copy(enough, read);
|
|
_largeBuffer = std::move(enough);
|
|
_usingLargeBuffer = true;
|
|
}
|
|
_offsetBytes = 0;
|
|
}
|
|
|
|
void TcpConnection::socketRead() {
|
|
Expects(_leftBytes > 0 || !_usingLargeBuffer);
|
|
|
|
if (!_socket || !_socket->isConnected()) {
|
|
CONNECTION_LOG_ERROR("Socket not connected in socketRead()");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"socket not connected while reading"_q,
|
|
});
|
|
error(kErrorCodeOther);
|
|
return;
|
|
}
|
|
|
|
if (_smallBuffer.empty()) {
|
|
_smallBuffer.resize(kSmallBufferSize);
|
|
}
|
|
do {
|
|
const auto readLimit = (_leftBytes > 0)
|
|
? _leftBytes
|
|
: (kSmallBufferSize - _offsetBytes - _readBytes);
|
|
Assert(readLimit > 0);
|
|
|
|
auto &buffer = _usingLargeBuffer ? _largeBuffer : _smallBuffer;
|
|
const auto full = bytes::make_span(buffer).subspan(_offsetBytes);
|
|
const auto free = full.subspan(_readBytes);
|
|
const auto readCount = _socket->read(free.subspan(0, readLimit));
|
|
if (readCount > 0) {
|
|
const auto read = free.subspan(0, readCount);
|
|
aesCtrEncrypt(read, _receiveKey, &_receiveState);
|
|
CONNECTION_LOG_INFO(u"Read %1 bytes"_q.arg(readCount));
|
|
|
|
_readBytes += readCount;
|
|
if (_leftBytes > 0) {
|
|
Assert(readCount <= _leftBytes);
|
|
_leftBytes -= readCount;
|
|
if (!_leftBytes) {
|
|
socketPacket(full.subspan(0, _readBytes));
|
|
if (!_socket || !_socket->isConnected()) {
|
|
return;
|
|
}
|
|
|
|
_usingLargeBuffer = false;
|
|
_largeBuffer.clear();
|
|
_offsetBytes = _readBytes = 0;
|
|
} else {
|
|
CONNECTION_LOG_INFO(
|
|
u"Not enough %1 for packet! read %2"_q
|
|
.arg(_leftBytes)
|
|
.arg(_readBytes));
|
|
receivedSome();
|
|
}
|
|
} else {
|
|
auto available = full.subspan(0, _readBytes);
|
|
while (_readBytes > 0) {
|
|
const auto packetSize = _protocol->readPacketLength(
|
|
available);
|
|
if (packetSize == Protocol::kUnknownSize) {
|
|
// Not enough bytes yet.
|
|
break;
|
|
} else if (packetSize <= 0) {
|
|
CONNECTION_LOG_ERROR(
|
|
u"Bad packet size in 4 bytes: %1"_q
|
|
.arg(packetSize));
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"bad packet size while reading"_q,
|
|
});
|
|
error(kErrorCodeOther);
|
|
return;
|
|
} else if (available.size() >= packetSize) {
|
|
socketPacket(available.subspan(0, packetSize));
|
|
if (!_socket || !_socket->isConnected()) {
|
|
return;
|
|
}
|
|
|
|
available = available.subspan(packetSize);
|
|
_offsetBytes += packetSize;
|
|
_readBytes -= packetSize;
|
|
|
|
// If we have too little space left in the buffer.
|
|
ensureAvailableInBuffer(kMinPacketBuffer);
|
|
} else {
|
|
_leftBytes = packetSize - available.size();
|
|
|
|
// If the next packet won't fit in the buffer.
|
|
ensureAvailableInBuffer(packetSize);
|
|
|
|
CONNECTION_LOG_INFO(u"Not enough %1 for packet! "
|
|
"full size %2 read %3"_q
|
|
.arg(_leftBytes)
|
|
.arg(packetSize)
|
|
.arg(available.size()));
|
|
receivedSome();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (readCount < 0) {
|
|
CONNECTION_LOG_ERROR(u"Socket read return %1."_q.arg(readCount));
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"socket read failed"_q,
|
|
});
|
|
error(kErrorCodeOther);
|
|
return;
|
|
} else {
|
|
CONNECTION_LOG_INFO(
|
|
"No bytes read, but bytes available was true...");
|
|
break;
|
|
}
|
|
} while (_socket
|
|
&& _socket->isConnected()
|
|
&& _socket->hasBytesAvailable());
|
|
|
|
if (_socket
|
|
&& _status == Status::Ready
|
|
&& !_leftBytes
|
|
&& !_readBytes
|
|
&& _socket->takeRotation()) {
|
|
// A throttled tunnel freezes each connection after ~16 KB, so a file
|
|
// connection is reopened between packets instead: the session
|
|
// reconnects at once and resends whatever is still unanswered.
|
|
CONNECTION_LOG_INFO("Rotating the connection at a packet boundary.");
|
|
_rotating = true;
|
|
disconnected();
|
|
}
|
|
}
|
|
|
|
mtpBuffer TcpConnection::parsePacket(bytes::const_span bytes) {
|
|
const auto packet = _protocol->readPacket(bytes);
|
|
CONNECTION_LOG_INFO(u"Packet received, size = %1."_q.arg(packet.size()));
|
|
const auto primes = packet.size() / sizeof(mtpPrime);
|
|
Assert(primes > 0);
|
|
if (primes < 3) {
|
|
// nop or error or new quickack, latter is not yet supported.
|
|
const auto first = binary::Read<mtpPrime>(packet);
|
|
if (first != 0) {
|
|
CONNECTION_LOG_ERROR(u"Error packet received, code = %1"_q
|
|
.arg(first));
|
|
}
|
|
return mtpBuffer(1, first);
|
|
}
|
|
auto result = mtpBuffer(primes);
|
|
binary::Copy(
|
|
bytes::make_span(result),
|
|
packet.subspan(0, primes * sizeof(mtpPrime)));
|
|
return result;
|
|
}
|
|
|
|
void TcpConnection::socketConnected() {
|
|
Expects(_status == Status::Waiting);
|
|
|
|
auto buffer = preparePQFake(_checkNonce);
|
|
|
|
CONNECTION_LOG_INFO("Socket connected; sending fake req_pq.");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::TelegramCheck,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"checking telegram through proxy"_q,
|
|
});
|
|
|
|
_pingTime = crl::now();
|
|
sendData(std::move(buffer), {});
|
|
}
|
|
|
|
void TcpConnection::socketDisconnected() {
|
|
CONNECTION_LOG_INFO("Socket disconnected.");
|
|
if (_status == Status::Waiting || _status == Status::Ready) {
|
|
disconnected();
|
|
}
|
|
}
|
|
|
|
void TcpConnection::sendData(mtpBuffer &&buffer, SendDataContext) {
|
|
Expects(buffer.size() > 2);
|
|
|
|
if (!_socket) {
|
|
return;
|
|
}
|
|
char connectionStartPrefixBytes[kConnectionStartPrefixSize];
|
|
const auto connectionStartPrefix = prepareConnectionStartPrefix(
|
|
bytes::make_span(connectionStartPrefixBytes));
|
|
|
|
// buffer: 2 available int-s + data + available int.
|
|
const auto bytes = _protocol->finalizePacket(buffer);
|
|
CONNECTION_LOG_INFO(u"TCP Info: write packet %1 bytes."_q
|
|
.arg(bytes.size()));
|
|
aesCtrEncrypt(bytes, _sendKey, &_sendState);
|
|
_socket->write(connectionStartPrefix, bytes);
|
|
}
|
|
|
|
bytes::const_span TcpConnection::prepareConnectionStartPrefix(
|
|
bytes::span buffer) {
|
|
Expects(_socket != nullptr);
|
|
Expects(_protocol != nullptr);
|
|
|
|
if (_connectionStarted) {
|
|
return {};
|
|
}
|
|
_connectionStarted = true;
|
|
|
|
// prepare random part
|
|
char nonceBytes[64];
|
|
const auto nonce = bytes::make_span(nonceBytes);
|
|
do {
|
|
bytes::set_random(nonce);
|
|
} while (!_socket->isGoodStartNonce(nonce));
|
|
|
|
// prepare encryption key/iv
|
|
_protocol->prepareKey(
|
|
bytes::make_span(_sendKey),
|
|
nonce.subspan(8, CTRState::KeySize));
|
|
bytes::copy(
|
|
bytes::make_span(_sendState.ivec),
|
|
nonce.subspan(8 + CTRState::KeySize, CTRState::IvecSize));
|
|
|
|
// prepare decryption key/iv
|
|
auto reversedBytes = bytes::vector(48);
|
|
const auto reversed = bytes::make_span(reversedBytes);
|
|
bytes::copy(reversed, nonce.subspan(8, reversed.size()));
|
|
std::reverse(reversed.begin(), reversed.end());
|
|
_protocol->prepareKey(
|
|
bytes::make_span(_receiveKey),
|
|
reversed.subspan(0, CTRState::KeySize));
|
|
bytes::copy(
|
|
bytes::make_span(_receiveState.ivec),
|
|
reversed.subspan(CTRState::KeySize, CTRState::IvecSize));
|
|
|
|
binary::WriteAt<uint32>(nonce, 56, _protocol->id());
|
|
binary::WriteAt<int16>(nonce, 60, _protocolDcId);
|
|
|
|
bytes::copy(buffer, nonce.subspan(0, 56));
|
|
aesCtrEncrypt(nonce, _sendKey, &_sendState);
|
|
bytes::copy(buffer.subspan(56), nonce.subspan(56));
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void TcpConnection::disconnectFromServer() {
|
|
if (_status == Status::Finished) {
|
|
return;
|
|
}
|
|
_status = Status::Finished;
|
|
_connectedLifetime.destroy();
|
|
_lifetime.destroy();
|
|
_socket = nullptr;
|
|
}
|
|
|
|
void TcpConnection::connectToServer(
|
|
const QString &address,
|
|
int port,
|
|
const bytes::vector &protocolSecret,
|
|
int16 protocolDcId,
|
|
bool protocolForFiles,
|
|
ConnectionStartContext context) {
|
|
Expects(_address.isEmpty());
|
|
Expects(_port == 0);
|
|
Expects(_protocol == nullptr);
|
|
Expects(_protocolDcId == 0);
|
|
|
|
_mtproxyAttempt = context.mtproxyAttempt;
|
|
_mtproxyPlan = context.mtproxyPlan;
|
|
_mtproxyAttemptStartedAt = context.mtproxyAttemptStartedAt;
|
|
const auto proxyProtocol = (_proxy.type == ProxyData::Type::Mtproto)
|
|
|| (_proxy.type == ProxyData::Type::Web);
|
|
const auto secret = proxyProtocol
|
|
? _proxy.secretFromMtprotoPassword()
|
|
: protocolSecret;
|
|
_transport = (_proxy.type == ProxyData::Type::Socks5)
|
|
? TransportMode::Socks5
|
|
: (_proxy.type == ProxyData::Type::Http)
|
|
? TransportMode::Http
|
|
: (_proxy.type == ProxyData::Type::Mtproto)
|
|
? ((secret.size() >= 21 && secret[0] == bytes::type(0xEE))
|
|
? TransportMode::FakeTlsMtproxy
|
|
: TransportMode::PlainMtproxy)
|
|
: TransportMode::Direct;
|
|
if (proxyProtocol) {
|
|
_address = _proxy.host;
|
|
_port = _proxy.port;
|
|
_protocol = Protocol::Create(secret);
|
|
} else {
|
|
_address = address;
|
|
_port = port;
|
|
_protocol = Protocol::Create(secret);
|
|
}
|
|
_socket = CreateProxyAwareSocket(
|
|
_runtime,
|
|
thread(),
|
|
secret,
|
|
_proxy,
|
|
protocolForFiles,
|
|
_stealth,
|
|
protocolDcId,
|
|
_mtproxyAttempt,
|
|
_mtproxyPlan,
|
|
_mtproxyAttemptStartedAt);
|
|
_protocolDcId = protocolDcId;
|
|
|
|
const auto postfix = _socket->debugPostfix();
|
|
_debugId = u"%1(dc:%2,%3%4:%5%6)"_q
|
|
.arg(_debugId.toInt())
|
|
.arg(
|
|
ProtocolDcDebugId(_protocolDcId),
|
|
(_proxy.type == ProxyData::Type::Mtproto)
|
|
? "mtproxy "
|
|
: (_proxy.type == ProxyData::Type::Web)
|
|
? "webproxy "
|
|
: "",
|
|
_address)
|
|
.arg(_port)
|
|
.arg(postfix.isEmpty() ? _protocol->debugPostfix() : postfix);
|
|
_mtproxyAttempt.connectionId = _debugId;
|
|
_runtime->proxyEndpointContext().updateTraceAttempt(_mtproxyAttempt);
|
|
_socket->setDebugId(_debugId);
|
|
|
|
CONNECTION_LOG_INFO("Connecting...");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Connecting,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"connecting to proxy"_q,
|
|
});
|
|
|
|
_socket->connected(
|
|
) | rpl::on_next([=] {
|
|
socketConnected();
|
|
}, _connectedLifetime);
|
|
|
|
_socket->disconnected(
|
|
) | rpl::on_next([=] {
|
|
socketDisconnected();
|
|
}, _lifetime);
|
|
|
|
_socket->readyRead(
|
|
) | rpl::on_next([=] {
|
|
socketRead();
|
|
}, _lifetime);
|
|
|
|
_socket->error(
|
|
) | rpl::on_next([=](int errorCode) {
|
|
socketError(errorCode);
|
|
}, _lifetime);
|
|
|
|
_socket->progress(
|
|
) | rpl::on_next([=](HandshakePhase phase) {
|
|
socketProgress(phase);
|
|
}, _lifetime);
|
|
|
|
_socket->syncTimeRequests(
|
|
) | rpl::on_next([=] {
|
|
syncTimeRequest();
|
|
}, _lifetime);
|
|
|
|
_socket->connectToHost(_address, _port);
|
|
}
|
|
|
|
crl::time TcpConnection::pingTime() const {
|
|
return isConnected() ? _pingTime : crl::time(0);
|
|
}
|
|
|
|
crl::time TcpConnection::fullConnectTimeout() const {
|
|
return (_proxy.type == ProxyData::Type::Web)
|
|
? kWebProxyFullConnectionTimeout
|
|
: kFullConnectionTimeout;
|
|
}
|
|
|
|
void TcpConnection::socketPacket(bytes::const_span bytes) {
|
|
Expects(_socket != nullptr);
|
|
|
|
// old quickack?..
|
|
const auto data = parsePacket(bytes);
|
|
if (data.size() == 1) {
|
|
if (data[0] != 0) {
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"bad proxy response"_q,
|
|
});
|
|
error(data[0]);
|
|
} else {
|
|
// nop
|
|
}
|
|
//} else if (data.size() == 2) {
|
|
// new quickack?..
|
|
} else if (_status == Status::Ready) {
|
|
_receivedQueue.push_back(data);
|
|
receivedData();
|
|
} else if (_status == Status::Waiting) {
|
|
if (const auto res_pq = readPQFakeReply(data)) {
|
|
const auto &data = res_pq->c_resPQ();
|
|
if (data.vnonce() == _checkNonce) {
|
|
CONNECTION_LOG_INFO("Valid pq response by TCP.");
|
|
_status = Status::Ready;
|
|
_connectedLifetime.destroy();
|
|
_pingTime = (crl::now() - _pingTime);
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Connected,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"proxy connected"_q,
|
|
});
|
|
connected();
|
|
} else {
|
|
CONNECTION_LOG_ERROR(
|
|
"Wrong nonce received in TCP fake pq-responce");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"wrong nonce in proxy response"_q,
|
|
});
|
|
error(kErrorCodeOther);
|
|
}
|
|
} else {
|
|
CONNECTION_LOG_ERROR("Could not parse TCP fake pq-responce");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"could not parse proxy response"_q,
|
|
});
|
|
error(kErrorCodeOther);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TcpConnection::timedOut() {
|
|
CONNECTION_LOG_ERROR("Connection timed out.");
|
|
if (_socket) {
|
|
_socket->timedOut();
|
|
}
|
|
_timeoutFailure = _socket
|
|
? _socket->proxyTransportFailure()
|
|
: ProxyTransportFailure();
|
|
if (_proxy.type == ProxyData::Type::Mtproto
|
|
&& _timeoutFailure.reason == ProxyMtproxyTerminalReason::None) {
|
|
_timeoutFailure.reason = (_socket && _socket->isConnected())
|
|
? ProxyMtproxyTerminalReason::ConnectedNoMtprotoData
|
|
: ProxyMtproxyTerminalReason::TcpConnectTimeout;
|
|
_timeoutFailure.error = ProxyConnectionError::Timeout;
|
|
_timeoutFailure.closeOrigin = ProxyCloseOrigin::LocalTimeout;
|
|
_timeoutFailure.attribution = ProxyFailureAttribution::Unclear;
|
|
}
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::Timeout,
|
|
.mtproxyReason = _timeoutFailure.reason,
|
|
.attempt = _mtproxyAttempt,
|
|
.terminalUntil = _socket
|
|
? _socket->mtproxyTerminalUntil()
|
|
: 0,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"proxy connection timed out"_q,
|
|
});
|
|
}
|
|
|
|
void TcpConnection::markProxyMtprotoPayloadReceived() {
|
|
if (_socket) {
|
|
_socket->markProxyMtprotoPayloadReceived();
|
|
}
|
|
}
|
|
|
|
HandshakePhase TcpConnection::handshakePhase() const {
|
|
if (!_socket) {
|
|
return HandshakePhase::None;
|
|
}
|
|
const auto phase = _socket->handshakePhase();
|
|
return (phase != HandshakePhase::None || !_socket->isConnected())
|
|
? phase
|
|
: HandshakePhase::TcpConnected;
|
|
}
|
|
|
|
ProxyConnectionAttempt TcpConnection::proxyConnectionAttempt() const {
|
|
return _mtproxyAttempt;
|
|
}
|
|
|
|
ProxyTransportFailure TcpConnection::proxyTransportFailure() const {
|
|
return (_timeoutFailure.reason != ProxyMtproxyTerminalReason::None)
|
|
? _timeoutFailure
|
|
: _socket
|
|
? _socket->proxyTransportFailure()
|
|
: ProxyTransportFailure();
|
|
}
|
|
|
|
ReceiveWaitVerdict TcpConnection::receiveWaitVerdict(
|
|
crl::time waitStartedAt) const {
|
|
return (_socket && _status == Status::Ready)
|
|
? _socket->receiveWaitVerdict(waitStartedAt)
|
|
: ReceiveWaitVerdict();
|
|
}
|
|
|
|
bool TcpConnection::isConnected() const {
|
|
return (_status == Status::Ready);
|
|
}
|
|
|
|
int32 TcpConnection::debugState() const {
|
|
return _socket ? _socket->debugState() : -1;
|
|
}
|
|
|
|
QString TcpConnection::transport() const {
|
|
if (!isConnected()) {
|
|
return QString();
|
|
}
|
|
auto result = _socket ? _socket->transportName() : u"TCP"_q;
|
|
if (qthelp::is_ipv6(_address)) {
|
|
result += u"/IPv6"_q;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString TcpConnection::tag() const {
|
|
auto result = u"TCP"_q;
|
|
if (qthelp::is_ipv6(_address)) {
|
|
result += u"/IPv6"_q;
|
|
} else {
|
|
result += u"/IPv4"_q;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void TcpConnection::socketError(int errorCode) {
|
|
if (!_socket) {
|
|
return;
|
|
}
|
|
|
|
CONNECTION_LOG_ERROR(u"Socket error %1."_q.arg(errorCode));
|
|
const auto proxyError = SocketProxyConnectionError(errorCode);
|
|
const auto transport = _socket->transportName();
|
|
if (transport == u"WSS"_q
|
|
&& proxyError == ProxyConnectionError::RemoteClosed) {
|
|
NoteProxyWssRemoteClosed(_runtime, _proxy);
|
|
}
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = proxyError,
|
|
.mtproxyReason = _socket->mtproxyTerminalReason(),
|
|
.attempt = _mtproxyAttempt,
|
|
.terminalUntil = _socket->mtproxyTerminalUntil(),
|
|
.proxy = _proxy,
|
|
.transport = (transport == u"WSS"_q) ? transport : tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"proxy socket error"_q,
|
|
});
|
|
error(errorCode);
|
|
}
|
|
|
|
void TcpConnection::socketProgress(HandshakePhase phase) {
|
|
if (phase != HandshakePhase::None) {
|
|
handshakeProgress();
|
|
}
|
|
switch (phase) {
|
|
case HandshakePhase::None:
|
|
return;
|
|
|
|
case HandshakePhase::TcpConnected:
|
|
CONNECTION_LOG_INFO("mtproxy tcp_connected");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::TcpConnected,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"tcp connected"_q,
|
|
});
|
|
return;
|
|
|
|
case HandshakePhase::ClientHelloSent:
|
|
CONNECTION_LOG_INFO("mtproxy client_hello_sent");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::ClientHelloSent,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"client hello sent"_q,
|
|
});
|
|
return;
|
|
|
|
case HandshakePhase::ServerHelloOk:
|
|
CONNECTION_LOG_INFO("mtproxy server_hello_hmac_ok");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::ServerHelloOk,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"server hello hmac ok"_q,
|
|
});
|
|
return;
|
|
|
|
case HandshakePhase::FirstDataReceived:
|
|
CONNECTION_LOG_INFO("mtproxy first_tls_app_recv");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::TelegramCheck,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = tag(),
|
|
.connectionId = _debugId,
|
|
.message = u"first tls app data received"_q,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
TcpConnection::~TcpConnection() = default;
|
|
|
|
} // namespace details
|
|
} // namespace MTP
|