ZaStoGram_desktop/Telegram/SourceFiles/mtproto/proxy/diagnostics.h
loop-uh c8120a7a6d
All checks were successful
Desktop source guards / guards (push) Successful in 8s
Treat the WEB proxy carrier as one shared pipe
Every MTProto session through a WEB proxy is a stream on a single
carrier whose relay keeps one FIFO per direction. Bulk media and
uploads filled that FIFO, every session's own 4-8s receive timer then
fired at once, and the reconnects piled new OPENs behind the same
backlog.

- Uplink: a scheduler serves interactive streams first, download
  requests next and uploads round-robin in 64 KiB frames, with at most
  1 MiB of upload bytes uncredited by the relay (one bounded batch in
  front of a chat request); a burst guard keeps uploads from starving.
- Downlink: download streams keep only a share of a 2 MiB credit budget
  (256 KiB..1 MiB each) instead of the full 4 MiB window, so media waits
  in the backend's TCP buffers rather than in front of chat replies.
- Liveness: a WEB session asks the carrier before dropping its stream.
  It waits while its request is still queued or the downlink is busy,
  fails when the pipe is quiet for 8s after delivery, busy for 30s, or
  after 64s total. A carrier with bytes outstanding and no relay
  progress for 20s is recovered once, for all streams.
- A WEB session opens one stream instead of racing identical ones, and
  a -404 on a WEB stream reconnects first; only a second -404 before any
  reply decrypts destroys the temporary key.
- Uploads and downloads use at most two sessions per DC through a WEB
  proxy, and slow upload parts are no longer cancelled and moved.
- web_carrier diagnostics report carrier state, stalls, per-class
  stream and queue counts, credit and throughput every 10s of activity.

The policy lives in web_proxy_flow.{h,cpp} and is covered by the new
test_web_proxy_flow target and a source guard.
2026-09-23 20:29:51 +03:00

291 lines
8 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
*/
#pragma once
#include "mtproto/proxy/status.h"
#include <QtCore/QDateTime>
#include <QtCore/QString>
#include <optional>
namespace MTP {
class RuntimeEnvironment;
enum class ProxyDiagnosticsSource {
MTProxy,
Network,
MTP,
};
enum class ProxyDiagnosticsPhase {
None,
Resolving,
Connecting,
TcpConnected,
ClientHelloSent,
ServerHelloOk,
TelegramCheck,
Connected,
Failed,
ProxyCheckStarted,
ProxyCheckFinished,
AdmissionQueued,
AdmissionStarted,
AdmissionCancelled,
RouteSelected,
RouteFailed,
CanonicalDegraded,
CanonicalRecovered,
StealthRecipeApplied,
TransportFallbackApplied,
RotationSwitched,
MtpConnecting,
MtpTransportReady,
MtpKeyCreating,
MtpKeyReady,
MtpFirstDataReceived,
MtpReceiveTimeout,
MtpConnectTimeout,
MtpBrokerTimeout,
MtpPingTimeout,
MtpBindFailed,
MtpKeyDestroyed,
MtpRestart,
AttemptSummary,
Liveness,
FileRpc,
FileProgress,
WebCarrier,
};
enum class ProxyDiagnosticsSeverity {
Info,
Warning,
Error,
};
enum class ProxyDiagnosticsTransition {
None,
Cancelled,
Queued,
SendAdmitted,
Sent,
Resent,
Result,
Error,
Slow,
Accepted,
Acknowledged,
};
enum class ProxyDiagnosticsErrorClass {
None,
BadRequest,
Unauthorized,
Forbidden,
NotFound,
NotAcceptable,
Flood,
Server,
Transport,
Unknown,
};
enum class ProxyDiagnosticsDirection {
None,
Download,
Upload,
};
enum class ProxyDiagnosticsRpcKind {
None,
GetFile,
GetWebFile,
GetCdnFile,
GetCdnFileHashes,
ReuploadCdnFile,
SaveFilePart,
SaveBigFilePart,
};
struct ProxyDiagnosticsEvent {
ProxyDiagnosticsSource source = ProxyDiagnosticsSource::Network;
ProxyDiagnosticsPhase phase = ProxyDiagnosticsPhase::None;
ProxyDiagnosticsSeverity severity = ProxyDiagnosticsSeverity::Info;
ProxyConnectionError error = ProxyConnectionError::None;
ProxyMtproxyTerminalReason mtproxyReason
= ProxyMtproxyTerminalReason::None;
ProxyConnectionAttempt attempt;
crl::time terminalUntil = 0;
ProxyData proxy;
QString transport;
QString dc;
QString connectionId;
QString socketId;
QString message;
QString canonical;
QString route;
QString proxyKeyHash;
QString profile;
QString configuredProfile;
QString effectiveProfile;
std::optional<int> recipeLevel;
std::optional<bool> pskOffered;
std::optional<bool> fragmentedClientHello;
QString phaseAtFailure;
std::optional<crl::time> queueMs;
std::optional<int> clientHelloBytes;
std::optional<int> clientHelloWrites;
std::optional<qint64> clientHelloAcceptedBytes;
std::optional<int> clientHelloFragmentSplit;
std::optional<crl::time> clientHelloFragmentDelayMs;
std::optional<qint64> rxAfterClientHello;
// Filled in when a handshake times out. Zero received bytes reads the same
// whether the answer never arrived or arrived and was never handed to us,
// and those two have nothing in common: one is the network, the other is
// this process. The socket's own state, its unread backlog and the number
// of times it announced something to read tell them apart.
std::optional<int> socketState;
std::optional<qint64> socketBytesAvailable;
std::optional<int> readNotifications;
QString rxClass;
QString block;
QString tlsRecordType;
QString tlsRecordVersion;
std::optional<int> tlsRecordLength;
QString responsePrefixHash;
std::optional<int> sniLength;
QString sniHash;
// The time that went into the hello's digest, and whether any reference
// stood behind it. A skew reported without the reference is unreadable:
// with no reference it is zero however wrong the machine's clock is.
std::optional<TimeId> clientHelloTimestamp;
QString clockReference;
std::optional<TimeId> clockSkew;
QString parserStage;
std::optional<ProxyCloseOrigin> closeOrigin;
std::optional<crl::time> dnsMs;
std::optional<crl::time> tcpMs;
std::optional<crl::time> firstRxMs;
std::optional<crl::time> serverHelloMs;
std::optional<crl::time> appDataMs;
std::optional<crl::time> mtprotoMs;
std::optional<crl::time> totalMs;
int traceSchema = 0;
QDateTime timestamp;
ProxyDiagnosticsTransition transition = ProxyDiagnosticsTransition::None;
ProxyDiagnosticsErrorClass errorClass
= ProxyDiagnosticsErrorClass::None;
ProxyDiagnosticsDirection direction = ProxyDiagnosticsDirection::None;
ProxyDiagnosticsRpcKind rpcKind = ProxyDiagnosticsRpcKind::None;
uint64 laneOrdinal = 0;
uint64 requestOrdinal = 0;
std::optional<int> sendCount;
std::optional<int> errorCode;
std::optional<qint64> acceptedBytes;
std::optional<qint64> acknowledgedBytes;
std::optional<bool> firstInLane;
std::optional<bool> isFinal;
};
struct ProxyEventReport {
ProxyDiagnosticsPhase phase = ProxyDiagnosticsPhase::None;
ProxyConnectionError error = ProxyConnectionError::None;
ProxyMtproxyTerminalReason mtproxyReason
= ProxyMtproxyTerminalReason::None;
ProxyConnectionAttempt attempt;
crl::time terminalUntil = 0;
std::optional<ProxyDiagnosticsSeverity> severity;
ProxyData proxy;
QString transport;
QString dc;
QString connectionId;
QString message;
QString canonical;
QString route;
QString proxyKeyHash;
QString profile;
QString configuredProfile;
QString effectiveProfile;
std::optional<int> recipeLevel;
std::optional<bool> pskOffered;
std::optional<bool> fragmentedClientHello;
QString phaseAtFailure;
std::optional<crl::time> queueMs;
std::optional<int> clientHelloBytes;
std::optional<int> clientHelloWrites;
std::optional<qint64> clientHelloAcceptedBytes;
std::optional<int> clientHelloFragmentSplit;
std::optional<crl::time> clientHelloFragmentDelayMs;
std::optional<qint64> rxAfterClientHello;
// Filled in when a handshake times out. Zero received bytes reads the same
// whether the answer never arrived or arrived and was never handed to us,
// and those two have nothing in common: one is the network, the other is
// this process. The socket's own state, its unread backlog and the number
// of times it announced something to read tell them apart.
std::optional<int> socketState;
std::optional<qint64> socketBytesAvailable;
std::optional<int> readNotifications;
QString rxClass;
QString block;
QString tlsRecordType;
QString tlsRecordVersion;
std::optional<int> tlsRecordLength;
QString responsePrefixHash;
std::optional<int> sniLength;
QString sniHash;
// The time that went into the hello's digest, and whether any reference
// stood behind it. A skew reported without the reference is unreadable:
// with no reference it is zero however wrong the machine's clock is.
std::optional<TimeId> clientHelloTimestamp;
QString clockReference;
std::optional<TimeId> clockSkew;
QString parserStage;
std::optional<ProxyCloseOrigin> closeOrigin;
std::optional<crl::time> dnsMs;
std::optional<crl::time> tcpMs;
std::optional<crl::time> firstRxMs;
std::optional<crl::time> serverHelloMs;
std::optional<crl::time> appDataMs;
std::optional<crl::time> mtprotoMs;
std::optional<crl::time> totalMs;
int traceSchema = 0;
};
[[nodiscard]] QString ProxyDiagnosticsKeyHash(const QString &key);
[[nodiscard]] QString ProxyDiagnosticsProxyKeyHash(const ProxyData &proxy);
[[nodiscard]] QString ProxyDiagnosticsEndpointText(
const QString &host,
int port);
[[nodiscard]] QString ProxyDiagnosticsTransportName(
const ProxyData &proxy,
ProxyTransport transport);
[[nodiscard]] QString ProxyDiagnosticsTlsProfileName(
ProxyTlsProfile profile);
[[nodiscard]] QString FormatProxyDiagnosticsEvent(
const ProxyDiagnosticsEvent &event);
[[nodiscard]] ProxyDiagnosticsSource SourceForReport(
const ProxyEventReport &report);
void WriteProxyDiagnosticsLine(
not_null<RuntimeEnvironment*> runtime,
ProxyDiagnosticsEvent event);
void ReportProxyEvent(
not_null<RuntimeEnvironment*> runtime,
ProxyEventReport report);
[[nodiscard]] bool ReportProxyAttemptSummary(
not_null<RuntimeEnvironment*> runtime,
ProxyEventReport report);
void ReportProxyLiveness(
not_null<RuntimeEnvironment*> runtime,
ProxyEventReport report);
} // namespace MTP